deadbyte-mcp 0.9.0 → 0.10.0
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 +119 -91
- package/README.txt +16 -11
- package/bench/fixtures/corpus.json +32 -13
- package/bench/fixtures/r36/case-map.json +8 -0
- package/bin/WORKER-REGISTRY.txt +3 -3
- package/bin/appcontainer-stage.exe +0 -0
- package/bin/appcontainer-stage.obj +0 -0
- package/bin/bootstrap-advapi32.exe +0 -0
- package/bin/bootstrap-advapi32.obj +0 -0
- package/bin/bootstrap-exitcode.exe +0 -0
- package/bin/bootstrap-exitcode.obj +0 -0
- package/bin/bootstrap-kernel32.exe +0 -0
- package/bin/bootstrap-kernel32.obj +0 -0
- package/bin/child-control-probe.exe +0 -0
- package/bin/child-control-probe.obj +0 -0
- package/bin/child-control-stage.exe +0 -0
- package/bin/child-control-stage.obj +0 -0
- package/bin/contained-reverse-worker.exe +0 -0
- package/bin/contained-reverse-worker.obj +0 -0
- package/bin/contained-transform-worker.exe +0 -0
- package/bin/contained-transform-worker.obj +0 -0
- package/bin/containment-probe.exe +0 -0
- package/bin/containment-probe.obj +0 -0
- package/bin/deadbyte-contain.exe +0 -0
- package/bin/deadbyte-contain.obj +0 -0
- package/bin/deadbyte-exec.exe +0 -0
- package/bin/deadbyte-exec.obj +0 -0
- package/bin/deadbyte-process-host.exe +0 -0
- package/bin/deadbyte-process-host.obj +0 -0
- package/bin/deadbyte-tunnel-host.exe +0 -0
- package/bin/deadbyte-tunnel-host.obj +0 -0
- package/controller/README.TXT +8 -6
- package/controller/deadbyte-controller.ps1 +48 -2
- package/controller/deadbyte-process-policy.json +5 -5
- package/docs/ARCHITECTURE.md +1 -1
- package/docs/R36-CAPABILITY-EVIDENCE.md +43 -0
- package/docs/superpowers/plans/2026-09-18-r36-zero-loss-autonomy.md +739 -0
- package/docs/superpowers/specs/2026-09-18-r36-zero-loss-autonomy-design.md +640 -0
- package/package.json +1 -1
- package/proof/CONTAINMENT-BUILD.txt +6 -6
- package/scripts/deferred-slot-operation.mjs +53 -15
- package/scripts/final-closure-verify.mjs +88 -39
- package/scripts/final-closure.mjs +63 -56
- package/scripts/gate-windows.ps1 +3 -3
- package/scripts/release-parity-tests.ps1 +9 -0
- package/scripts/release-parity.mjs +149 -139
- package/scripts/windows-gate-evidence.mjs +5 -5
- package/src/autonomous-context-engine.mjs +2 -1
- package/src/autonomous-mcp-tools.mjs +5 -1
- package/src/autonomous-output-contracts.mjs +4 -4
- package/src/autonomous-planner-contracts.mjs +36 -1
- package/src/autonomous-planner.mjs +25 -3
- package/src/autonomous-runtime.mjs +191 -21
- package/src/capability-benchmark-runner.mjs +86 -1
- package/src/capability-benchmark.mjs +45 -0
- package/src/capability-ledger.mjs +158 -0
- package/src/coding-mcp-tools.mjs +31 -17
- package/src/coding-output-contracts.mjs +48 -10
- package/src/coding-plane.mjs +45 -9
- package/src/coding-search.mjs +130 -47
- package/src/compact-gateway-mcp-tools.mjs +128 -0
- package/src/compact-gateway-output-contracts.mjs +16 -0
- package/src/deadbyte-cli.mjs +51 -28
- package/src/machine-fs-smoke-client.mjs +1 -1
- package/src/machine-fs.mjs +51 -8
- package/src/mcp-server.mjs +32 -9
- package/src/native-diff-v2.mjs +337 -0
- package/src/native-diff.mjs +50 -2
- package/src/observation-smoke-client.mjs +1 -1
- package/src/operator-surface.mjs +7 -1
- package/src/process-runtime.mjs +98 -11
- package/src/process-smoke-client.mjs +11 -3
- package/src/r36-compact-disarmed-probe.mjs +50 -0
- package/src/r36-full-surface-probe.mjs +56 -0
- package/src/remote-live-log.mjs +76 -0
- package/src/remote-mcp-autonomous-smoke-client.mjs +7 -3
- package/src/remote-mcp-coding-smoke-client.mjs +1 -1
- package/src/remote-mcp-r36-parity-client.mjs +175 -0
- package/src/remote-mcp-smoke-client.mjs +112 -56
- package/src/runtime-update.mjs +51 -1
- package/src/search-cursor.mjs +45 -0
- package/src/semantic-code.mjs +72 -5
- package/src/semantic-lsp-backend.mjs +39 -3
- package/src/version.mjs +1 -1
- package/test/autonomous-capability-r36.test.mjs +151 -0
- package/test/autonomous-runtime.test.mjs +2 -2
- package/test/capability-benchmark-r36.test.mjs +107 -0
- package/test/capability-benchmark-semantic-r36.test.mjs +52 -0
- package/test/capability-claims-r36.test.mjs +23 -0
- package/test/capability-ledger-r36.test.mjs +102 -0
- package/test/coding-catalog-consistency.test.mjs +2 -2
- package/test/coding-plane.test.mjs +2 -2
- package/test/coding-runtime.test.mjs +16 -0
- package/test/coding-search-r36.test.mjs +90 -0
- package/test/compact-catalog-r36.test.mjs +37 -0
- package/test/compact-gateway-r36.test.mjs +66 -0
- package/test/contained.test.mjs +1 -1
- package/test/core.test.mjs +1 -1
- package/test/deferred-slot-operation.test.mjs +9 -0
- package/test/fixtures/lsp-framed-server.mjs +2 -0
- package/test/helpers/autonomous-r34-fixture.mjs +5 -3
- package/test/multi-file-read.test.mjs +3 -0
- package/test/native-diff-topology-r36.test.mjs +105 -0
- package/test/package-boundary.test.mjs +6 -0
- package/test/process-longrun-r36.test.mjs +82 -0
- package/test/process-release-gate.test.mjs +9 -0
- package/test/production-docs.test.mjs +3 -3
- package/test/prompts-resources.test.mjs +7 -1
- package/test/r33-closeout-regression.test.mjs +4 -3
- package/test/r33-finalization.test.mjs +14 -9
- package/test/r36-finalization.test.mjs +104 -0
- package/test/r36-release-identity.test.mjs +52 -0
- package/test/release-version.test.mjs +18 -15
- package/test/remote-live-log-r36.test.mjs +37 -0
- package/test/remote-live-log-recovery-r36.test.mjs +41 -0
- package/test/remote-r24-catalog.test.mjs +13 -0
- package/test/remote-session-cli.test.mjs +40 -30
- package/test/runtime-self-update.test.mjs +58 -1
- package/test/semantic-lsp-backend.test.mjs +9 -0
- package/test/semantic-r36-rename.test.mjs +82 -0
- package/deadbyte-mcp-0.8.9.tgz +0 -0
package/src/process-runtime.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash, randomBytes, sign as cryptoSign, verify as cryptoVerify } from 'node:crypto';
|
|
2
|
-
import { appendFile, lstat, mkdir, readFile, realpath } from 'node:fs/promises';
|
|
2
|
+
import { appendFile, lstat, mkdir, readFile, readdir, realpath, writeFile } from 'node:fs/promises';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { spawn } from 'node:child_process';
|
|
5
5
|
import { canonicalJson } from './canonical-json.mjs';
|
|
@@ -13,6 +13,7 @@ const ZERO_SHA256 = '0'.repeat(64);
|
|
|
13
13
|
const SHA256_RE = /^[0-9a-f]{64}$/;
|
|
14
14
|
const READY_PREFIX = Buffer.from('DEADBYTE_PROCESS_HOST_READY ', 'utf8');
|
|
15
15
|
const START_TIMEOUT_MS = 8000;
|
|
16
|
+
const TERMINAL_RECOVERY_SCHEMA = 'deadbyte.process-terminal.v1';
|
|
16
17
|
|
|
17
18
|
const sha256Bytes = bytes => createHash('sha256').update(bytes).digest('hex');
|
|
18
19
|
const nowUtc = () => new Date().toISOString();
|
|
@@ -101,15 +102,85 @@ export function createProcessRuntime({ policy, signingKeyPath, verifyKeyPath })
|
|
|
101
102
|
const sessions = new Map();
|
|
102
103
|
let signingPromise = null;
|
|
103
104
|
let verifyPromise = null;
|
|
105
|
+
let hydrationPromise = null;
|
|
104
106
|
const runtime = {
|
|
105
107
|
policy,
|
|
106
108
|
signing: () => (signingPromise ??= loadPrivateSigningKey(signingKeyPath)),
|
|
107
109
|
verifying: () => (verifyPromise ??= loadPublicVerifyKey(verifyKeyPath))
|
|
108
110
|
};
|
|
109
111
|
|
|
110
|
-
function
|
|
112
|
+
async function readVerifiedJournal(sessionId, journalPath) {
|
|
113
|
+
const verifying = await runtime.verifying();
|
|
114
|
+
const text = await readFile(journalPath, 'utf8');
|
|
115
|
+
const records = text.split(/\r?\n/).filter(Boolean).map(line => JSON.parse(line));
|
|
116
|
+
let previous = ZERO_SHA256;
|
|
117
|
+
let sequence = 0;
|
|
118
|
+
for (const record of records) {
|
|
119
|
+
const { event_sha256: eventSha, key_id: keyId, signature, ...unsigned } = record;
|
|
120
|
+
if (unsigned.schema !== EVENT_SCHEMA || unsigned.session_id !== sessionId || unsigned.sequence !== sequence + 1 ||
|
|
121
|
+
unsigned.previous_event_sha256 !== previous || unsigned.policy_sha256 !== policy.policy_sha256) {
|
|
122
|
+
throw new Error('process event journal chain mismatch');
|
|
123
|
+
}
|
|
124
|
+
const canonical = Buffer.from(canonicalJson(unsigned), 'utf8');
|
|
125
|
+
const observedSha = sha256Bytes(canonical);
|
|
126
|
+
if (eventSha !== observedSha || keyId !== verifying.keyId || typeof signature !== 'string' || !cryptoVerify(null,
|
|
127
|
+
Buffer.concat([EVENT_DOMAIN, canonical]), verifying.publicKey, Buffer.from(signature, 'base64'))) {
|
|
128
|
+
throw new Error('process event journal signature mismatch');
|
|
129
|
+
}
|
|
130
|
+
previous = observedSha;
|
|
131
|
+
sequence += 1;
|
|
132
|
+
}
|
|
133
|
+
return { records, event_count: sequence, head_sha256: previous };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async function hydrateTerminalSessions() {
|
|
137
|
+
if (hydrationPromise) return hydrationPromise;
|
|
138
|
+
hydrationPromise = (async () => {
|
|
139
|
+
await mkdir(policy.state_dir, { recursive: true });
|
|
140
|
+
const names = (await readdir(policy.state_dir)).filter(name => /^[0-9a-f]{64}\.jsonl$/.test(name)).sort();
|
|
141
|
+
for (const name of names) {
|
|
142
|
+
const sessionId = name.slice(0, 64);
|
|
143
|
+
if (sessions.has(sessionId)) continue;
|
|
144
|
+
let checked;
|
|
145
|
+
try { checked = await readVerifiedJournal(sessionId, path.join(policy.state_dir, name)); }
|
|
146
|
+
catch { continue; }
|
|
147
|
+
const terminal = checked.records.at(-1);
|
|
148
|
+
if (!terminal || !['exited','terminated'].includes(terminal.type) || terminal.detail?.recovery_schema !== TERMINAL_RECOVERY_SCHEMA) continue;
|
|
149
|
+
const started = checked.records.find(record => record.type === 'started');
|
|
150
|
+
if (!started || !Number.isInteger(started.detail?.child_pid) || !/^[0-9]+$/.test(String(started.detail?.creation_filetime ?? ''))) continue;
|
|
151
|
+
const profile = policy.profiles[terminal.profile_id];
|
|
152
|
+
if (!profile || !policy.roots[terminal.root_id]) continue;
|
|
153
|
+
const outputFile = terminal.detail.output_file;
|
|
154
|
+
if (typeof outputFile !== 'string' || outputFile !== `${sessionId}.stream.bin`) continue;
|
|
155
|
+
let output;
|
|
156
|
+
try { output = await readFile(path.join(policy.state_dir, outputFile)); }
|
|
157
|
+
catch { continue; }
|
|
158
|
+
if (!Number.isInteger(terminal.detail.retained_output_bytes) || terminal.detail.retained_output_bytes !== output.length ||
|
|
159
|
+
!SHA256_RE.test(terminal.detail.retained_output_sha256 ?? '') || sha256Bytes(output) !== terminal.detail.retained_output_sha256 ||
|
|
160
|
+
!SHA256_RE.test(terminal.detail.stream_sha256 ?? '') || !Number.isInteger(terminal.detail.stream_head) ||
|
|
161
|
+
!Number.isInteger(terminal.detail.earliest_offset) || terminal.detail.earliest_offset < 0 ||
|
|
162
|
+
terminal.detail.stream_head < terminal.detail.earliest_offset || terminal.detail.stream_head - terminal.detail.earliest_offset !== output.length) continue;
|
|
163
|
+
sessions.set(sessionId, {
|
|
164
|
+
session_id:sessionId, job_id:null, profile_id:terminal.profile_id, root_id:terminal.root_id,
|
|
165
|
+
cwd_relative:typeof started.detail?.cwd === 'string' ? started.detail.cwd : '.',
|
|
166
|
+
child_pid:started.detail.child_pid, creation_filetime:String(started.detail.creation_filetime),
|
|
167
|
+
state:terminal.type, exit_code:Number.isInteger(terminal.detail.exit_code) ? terminal.detail.exit_code : null,
|
|
168
|
+
earliest_offset:terminal.detail.earliest_offset, stream_head:terminal.detail.stream_head,
|
|
169
|
+
stream_sha256:terminal.detail.stream_sha256, output, control_buffer:Buffer.alloc(0),
|
|
170
|
+
journal_head_sha256:checked.head_sha256, journal_path:path.join(policy.state_dir,name), event_count:checked.event_count,
|
|
171
|
+
max_retained_output_bytes:profile.max_retained_output_bytes, stderr:'', terminate_requested:terminal.type==='terminated',
|
|
172
|
+
protocol_error:null, transport:profile.transport, win32_input_mode:false, host:null, close_promise:Promise.resolve(),
|
|
173
|
+
recovered_terminal:true
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
})();
|
|
177
|
+
return hydrationPromise;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async function sessionFor(sessionId) {
|
|
111
181
|
if (typeof sessionId !== 'string' || !SHA256_RE.test(sessionId)) throw new Error('invalid process session id');
|
|
112
|
-
|
|
182
|
+
let session = sessions.get(sessionId);
|
|
183
|
+
if (!session) { await hydrateTerminalSessions(); session = sessions.get(sessionId); }
|
|
113
184
|
if (!session) throw new Error(`unknown process session '${sessionId}'`);
|
|
114
185
|
return session;
|
|
115
186
|
}
|
|
@@ -172,6 +243,7 @@ export function createProcessRuntime({ policy, signingKeyPath, verifyKeyPath })
|
|
|
172
243
|
}
|
|
173
244
|
|
|
174
245
|
async function start({ profileId, rootId, cwd = '.', args = [] }) {
|
|
246
|
+
await hydrateTerminalSessions();
|
|
175
247
|
const grant = await assertProcessArmed(policy, profileId, rootId);
|
|
176
248
|
const profile = policy.profiles[profileId];
|
|
177
249
|
if (!profile) throw new Error(`process profile '${profileId}' is not configured`);
|
|
@@ -209,8 +281,23 @@ export function createProcessRuntime({ policy, signingKeyPath, verifyKeyPath })
|
|
|
209
281
|
parseHostOutput(session, null, true);
|
|
210
282
|
if (session.state === 'starting') session.ready_reject?.(new Error(`process host exited before READY: ${session.stderr}`));
|
|
211
283
|
session.exit_code ??= Number.isInteger(code) ? code : null;
|
|
212
|
-
|
|
213
|
-
try {
|
|
284
|
+
const terminalState = session.terminate_requested ? 'terminated' : 'exited';
|
|
285
|
+
try {
|
|
286
|
+
const outputFile = `${session.session_id}.stream.bin`;
|
|
287
|
+
const retainedOutputSha256 = sha256Bytes(session.output);
|
|
288
|
+
await writeFile(path.join(policy.state_dir, outputFile), session.output, { flag:'wx' });
|
|
289
|
+
await appendEvent(runtime, session, terminalState, {
|
|
290
|
+
recovery_schema:TERMINAL_RECOVERY_SCHEMA,
|
|
291
|
+
exit_code:session.exit_code,
|
|
292
|
+
earliest_offset:session.earliest_offset,
|
|
293
|
+
stream_head:session.stream_head,
|
|
294
|
+
stream_sha256:session.stream_sha256,
|
|
295
|
+
retained_output_bytes:session.output.length,
|
|
296
|
+
retained_output_sha256:retainedOutputSha256,
|
|
297
|
+
output_file:outputFile
|
|
298
|
+
});
|
|
299
|
+
session.state = terminalState;
|
|
300
|
+
} catch {}
|
|
214
301
|
resolve();
|
|
215
302
|
});
|
|
216
303
|
});
|
|
@@ -223,7 +310,7 @@ export function createProcessRuntime({ policy, signingKeyPath, verifyKeyPath })
|
|
|
223
310
|
}
|
|
224
311
|
|
|
225
312
|
async function read({ sessionId, offset = 0, maxBytes = 65536 }) {
|
|
226
|
-
const session = sessionFor(sessionId);
|
|
313
|
+
const session = await sessionFor(sessionId);
|
|
227
314
|
if (!Number.isInteger(offset) || offset < 0 || !Number.isInteger(maxBytes) || maxBytes < 1 || maxBytes > 262144) throw new Error('invalid process read bounds');
|
|
228
315
|
if (offset > session.stream_head) throw new Error('process read offset is beyond stream head');
|
|
229
316
|
const truncated = offset < session.earliest_offset;
|
|
@@ -235,10 +322,10 @@ export function createProcessRuntime({ policy, signingKeyPath, verifyKeyPath })
|
|
|
235
322
|
truncated, data_base64: data.toString('base64'), state: session.state, exit_code: session.exit_code };
|
|
236
323
|
}
|
|
237
324
|
|
|
238
|
-
async function status({ sessionId }) { return publicSession(sessionFor(sessionId)); }
|
|
239
|
-
async function list() { return { sessions: [...sessions.values()].map(publicSession).sort((a,b) => a.session_id.localeCompare(b.session_id)) }; }
|
|
325
|
+
async function status({ sessionId }) { return publicSession(await sessionFor(sessionId)); }
|
|
326
|
+
async function list() { await hydrateTerminalSessions(); return { sessions: [...sessions.values()].map(publicSession).sort((a,b) => a.session_id.localeCompare(b.session_id)) }; }
|
|
240
327
|
async function action({ sessionId, actionId }) {
|
|
241
|
-
const session = sessionFor(sessionId);
|
|
328
|
+
const session = await sessionFor(sessionId);
|
|
242
329
|
if (session.state !== 'running') throw new Error('process action requires a running session');
|
|
243
330
|
await assertProcessArmed(policy, session.profile_id, session.root_id);
|
|
244
331
|
const profile = policy.profiles[session.profile_id];
|
|
@@ -259,7 +346,7 @@ export function createProcessRuntime({ policy, signingKeyPath, verifyKeyPath })
|
|
|
259
346
|
return { action_id: actionId, logical_bytes: logicalBytes.length, logical_sha256: logicalSha256, session: publicSession(session) };
|
|
260
347
|
}
|
|
261
348
|
async function terminate({ sessionId }) {
|
|
262
|
-
const session = sessionFor(sessionId);
|
|
349
|
+
const session = await sessionFor(sessionId);
|
|
263
350
|
if (['exited','terminated'].includes(session.state)) return publicSession(session);
|
|
264
351
|
session.terminate_requested = true;
|
|
265
352
|
session.host.kill();
|
|
@@ -268,7 +355,7 @@ export function createProcessRuntime({ policy, signingKeyPath, verifyKeyPath })
|
|
|
268
355
|
}
|
|
269
356
|
|
|
270
357
|
async function verifyEventJournal({ sessionId }) {
|
|
271
|
-
const session = sessionFor(sessionId);
|
|
358
|
+
const session = await sessionFor(sessionId);
|
|
272
359
|
const verifying = await runtime.verifying();
|
|
273
360
|
const text = await readFile(session.journal_path, 'utf8');
|
|
274
361
|
const records = text.split(/\r?\n/).filter(Boolean).map(line => JSON.parse(line));
|
|
@@ -14,10 +14,18 @@ const ZERO_SHA256 = '0'.repeat(64);
|
|
|
14
14
|
const sha256 = bytes => createHash('sha256').update(bytes).digest('hex');
|
|
15
15
|
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
16
16
|
const repoRoot = path.resolve(here, '..');
|
|
17
|
-
const
|
|
17
|
+
const productionPolicyPath = path.join(repoRoot, 'controller', 'deadbyte-process-policy.json');
|
|
18
18
|
const serverPath = path.join(repoRoot, 'src', 'mcp-server.mjs');
|
|
19
|
-
const policy = await loadProcessPolicy(policyPath);
|
|
20
19
|
const smokeTrust = await import('node:fs/promises').then(({ mkdtemp }) => mkdtemp(path.join(os.tmpdir(), 'deadbyte-process-smoke-')));
|
|
20
|
+
const smokePolicyPath = path.join(smokeTrust, 'smoke-policy.json');
|
|
21
|
+
const productionPolicy = JSON.parse(await readFile(productionPolicyPath, 'utf8'));
|
|
22
|
+
const smokePolicy = {
|
|
23
|
+
...productionPolicy,
|
|
24
|
+
grant_file: path.join(smokeTrust, 'process-grant.json'),
|
|
25
|
+
state_dir: path.join(smokeTrust, 'process-state')
|
|
26
|
+
};
|
|
27
|
+
await writeFile(smokePolicyPath, `${JSON.stringify(smokePolicy, null, 2)}\n`, { flag: 'wx' });
|
|
28
|
+
const policy = await loadProcessPolicy(smokePolicyPath);
|
|
21
29
|
const keys = await generateTrustKeypair({ privateKeyPath: path.join(smokeTrust, 'private.pem'), publicKeyPath: path.join(smokeTrust, 'public.pem') });
|
|
22
30
|
await mkdir(path.dirname(policy.grant_file), { recursive: true });
|
|
23
31
|
const grant = { schema: PROCESS_GRANT_SCHEMA, policy_sha256: policy.policy_sha256,
|
|
@@ -29,7 +37,7 @@ const client = new Client({ name: 'deadbyte-process-smoke', version: '0.8.0' },
|
|
|
29
37
|
{ versionNegotiation: { mode: { pin: '2026-07-28' } } });
|
|
30
38
|
const transport = new StdioClientTransport({
|
|
31
39
|
command: process.execPath, args: [serverPath], cwd: repoRoot,
|
|
32
|
-
env: { ...getDefaultEnvironment(), DEADBYTE_PROCESS_RUNTIME: '1', DEADBYTE_PROCESS_POLICY:
|
|
40
|
+
env: { ...getDefaultEnvironment(), DEADBYTE_PROCESS_RUNTIME: '1', DEADBYTE_PROCESS_POLICY: smokePolicyPath,
|
|
33
41
|
DEADBYTE_SIGNING_KEY: keys.privateKeyPath, DEADBYTE_VERIFY_KEY: keys.publicKeyPath },
|
|
34
42
|
stderr: 'pipe'
|
|
35
43
|
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { Client, StreamableHTTPClientTransport } from '@modelcontextprotocol/client';
|
|
5
|
+
import { R36_COMPACT_GATEWAYS, R36_COMPACT_TOOL_LIMIT } from './remote-mcp-r36-parity-client.mjs';
|
|
6
|
+
|
|
7
|
+
const sha256=bytes=>createHash('sha256').update(bytes).digest('hex');
|
|
8
|
+
function structured(result,label){
|
|
9
|
+
if(result.isError) throw new Error(`${label} returned error: ${JSON.stringify(result.structuredContent??result.content)}`);
|
|
10
|
+
if(!result.structuredContent||typeof result.structuredContent!=='object') throw new Error(`${label} missing structured output`);
|
|
11
|
+
return result.structuredContent;
|
|
12
|
+
}
|
|
13
|
+
function gateway(result,label){
|
|
14
|
+
const value=structured(result,label);
|
|
15
|
+
if(value.status!=='ok'||!value.result||typeof value.result!=='object') throw new Error(`${label} gateway status=${String(value.status)}`);
|
|
16
|
+
return value.result;
|
|
17
|
+
}
|
|
18
|
+
export async function probeR36CompactDisarmed(endpointText){
|
|
19
|
+
const endpoint=new URL(endpointText);
|
|
20
|
+
if(endpoint.protocol!=='https:') throw new Error('R36 compact disarmed probe requires HTTPS');
|
|
21
|
+
const client=new Client({name:'deadbyte-r36-compact-disarmed-probe',version:'0.10.0'},{versionNegotiation:{mode:{pin:'2026-07-28'}}});
|
|
22
|
+
const transport=new StreamableHTTPClientTransport(endpoint);
|
|
23
|
+
try{
|
|
24
|
+
await client.connect(transport);
|
|
25
|
+
const listed=await client.listTools();
|
|
26
|
+
const names=listed.tools.map(tool=>tool.name).sort();
|
|
27
|
+
if(names.length>R36_COMPACT_TOOL_LIMIT) throw new Error(`R36 compact catalog exceeds ${R36_COMPACT_TOOL_LIMIT}: ${names.length}`);
|
|
28
|
+
for(const name of R36_COMPACT_GATEWAYS) if(!names.includes(name)) throw new Error(`R36 compact gateway missing ${name}`);
|
|
29
|
+
for(const tool of listed.tools) if(tool.outputSchema?.type!=='object'||tool.outputSchema?.additionalProperties!==false||!tool.outputSchema?.properties) throw new Error(`tool ${tool.name} missing strict output schema`);
|
|
30
|
+
const manifest=gateway(await client.callTool({name:'capability_manifest',arguments:{}}),'capability_manifest');
|
|
31
|
+
if(manifest.profile!=='compact'||!/^[0-9a-f]{64}$/.test(String(manifest.ledger_sha256??''))) throw new Error('R36 compact manifest malformed');
|
|
32
|
+
const required=manifest.capabilities.filter(item=>item?.production===true&&item?.host_only!==true);
|
|
33
|
+
for(const item of required) if(typeof item.compact_tool!=='string'||!names.includes(item.compact_tool)) throw new Error(`compact route missing ${String(item.id)}`);
|
|
34
|
+
const coding=structured(await client.callTool({name:'coding_session_status',arguments:{}}),'coding_session_status');
|
|
35
|
+
const autonomous=structured(await client.callTool({name:'autonomous_session_status',arguments:{}}),'autonomous_session_status');
|
|
36
|
+
if(coding.armed!==false||autonomous.coding_armed!==false||autonomous.autonomous_armed!==false) throw new Error('R36 compact authority must be DISARMED');
|
|
37
|
+
const machine=gateway(await client.callTool({name:'machine_observe',arguments:{op:'machine_status'}}),'machine_status');
|
|
38
|
+
const nonce=`r36-final-${randomBytes(16).toString('hex')}`;
|
|
39
|
+
const ping=gateway(await client.callTool({name:'machine_observe',arguments:{op:'machine_ping',nonce}}),'machine_ping');
|
|
40
|
+
if(ping.nonce!==nonce||ping.machine_id!==machine.machine_id||ping.release_id!==machine.release_id||ping.release_manifest_sha256!==machine.release_manifest_sha256) throw new Error('R36 compact machine ping binding mismatch');
|
|
41
|
+
return {status:'passed',profile:'compact',tool_count:names.length,tools:names,catalog_sha256:sha256(Buffer.from(JSON.stringify(names),'utf8')),strict_output_schemas:true,ledger_sha256:manifest.ledger_sha256,capability_count:manifest.capabilities.length,production_non_host_capabilities:required.length,capability_closure:true,coding_armed:false,autonomous_armed:false,release_id:machine.release_id,release_manifest_sha256:machine.release_manifest_sha256,machine_status:machine,machine_ping:ping,nonce,protocol_revision:client.getNegotiatedProtocolVersion()};
|
|
42
|
+
} finally {await client.close().catch(()=>{});}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const invoked=process.argv[1]&&path.resolve(process.argv[1])===fileURLToPath(import.meta.url);
|
|
46
|
+
if(invoked){
|
|
47
|
+
const endpoint=process.argv[2];
|
|
48
|
+
if(!endpoint) throw new Error('usage: node src/r36-compact-disarmed-probe.mjs https://host/mcp/path');
|
|
49
|
+
probeR36CompactDisarmed(endpoint).then(v=>console.log(JSON.stringify(v,null,2))).catch(e=>{console.error(e instanceof Error?e.stack:String(e));process.exitCode=1;});
|
|
50
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { Client } from '@modelcontextprotocol/client';
|
|
6
|
+
import { StdioClientTransport, getDefaultEnvironment } from '@modelcontextprotocol/client/stdio';
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
8
|
+
import { R36_EXPECTED_TOOLS } from './remote-mcp-r36-parity-client.mjs';
|
|
9
|
+
|
|
10
|
+
const sha256=bytes=>createHash('sha256').update(bytes).digest('hex');
|
|
11
|
+
|
|
12
|
+
export async function probeR36FullSurface({packageRoot,generationRoot,stateRoot=path.join(os.homedir(),'.deadbyte-mcp')}={}){
|
|
13
|
+
const pkgRoot=packageRoot?path.resolve(packageRoot):path.resolve(path.dirname(fileURLToPath(import.meta.url)),'..');
|
|
14
|
+
const gen=path.resolve(generationRoot);
|
|
15
|
+
const semanticPath=path.join(gen,'semantic-lsp-config.json');
|
|
16
|
+
const semanticSha=sha256(await readFile(semanticPath));
|
|
17
|
+
const env={
|
|
18
|
+
...getDefaultEnvironment(),
|
|
19
|
+
DEADBYTE_STATE_ROOT:path.resolve(stateRoot),
|
|
20
|
+
DEADBYTE_CORE_ROOT:pkgRoot,
|
|
21
|
+
DEADBYTE_WORKSPACE_ROOT:path.join(pkgRoot,'workspaces'),
|
|
22
|
+
DEADBYTE_CAPABILITY_PROFILE:'full',
|
|
23
|
+
DEADBYTE_AUDIT_RUNTIME:'0',
|
|
24
|
+
DEADBYTE_MACHINE_RUNTIME:'1',
|
|
25
|
+
DEADBYTE_AGENT_RUNTIME:'1',
|
|
26
|
+
DEADBYTE_PROCESS_RUNTIME:'1',
|
|
27
|
+
DEADBYTE_PROCESS_POLICY:path.join(gen,'process-policy.json'),
|
|
28
|
+
DEADBYTE_CODING_RUNTIME:'1',
|
|
29
|
+
DEADBYTE_MACHINE_FS_RUNTIME:'1',
|
|
30
|
+
DEADBYTE_OBSERVATION_RUNTIME:'1',
|
|
31
|
+
DEADBYTE_CODING_POLICY:path.join(gen,'coding-policy.json'),
|
|
32
|
+
DEADBYTE_SEMANTIC_LSP_CONFIG:semanticPath,
|
|
33
|
+
DEADBYTE_SEMANTIC_LSP_CONFIG_SHA256:semanticSha,
|
|
34
|
+
DEADBYTE_AUTONOMOUS_RUNTIME:'1',
|
|
35
|
+
DEADBYTE_AUTONOMOUS_POLICY:path.join(gen,'autonomous-policy.json'),
|
|
36
|
+
DEADBYTE_PRIVILEGED_POLICY:path.join(gen,'host-policy.json')
|
|
37
|
+
};
|
|
38
|
+
const client=new Client({name:'deadbyte-r36-full-surface-probe',version:'0.10.0'},{versionNegotiation:{mode:{pin:'2026-07-28'}}});
|
|
39
|
+
const transport=new StdioClientTransport({command:process.execPath,args:[path.join(pkgRoot,'src','mcp-server.mjs')],cwd:pkgRoot,env,stderr:'pipe'});
|
|
40
|
+
try{
|
|
41
|
+
await client.connect(transport);
|
|
42
|
+
const listed=await client.listTools();
|
|
43
|
+
const names=listed.tools.map(tool=>tool.name).sort();
|
|
44
|
+
if(JSON.stringify(names)!==JSON.stringify(R36_EXPECTED_TOOLS)) throw new Error(`R36 full catalog mismatch count=${names.length}`);
|
|
45
|
+
for(const tool of listed.tools){
|
|
46
|
+
if(tool.outputSchema?.type!=='object'||tool.outputSchema?.additionalProperties!==false||!tool.outputSchema?.properties) throw new Error(`R36 full tool ${tool.name} missing strict output schema`);
|
|
47
|
+
}
|
|
48
|
+
return {status:'passed',profile:'full',tool_count:names.length,tools:names,catalog_sha256:sha256(Buffer.from(JSON.stringify(names),'utf8')),strict_output_schemas:true,protocol_revision:client.getNegotiatedProtocolVersion()};
|
|
49
|
+
} finally {await client.close().catch(()=>{});}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if(process.argv[1]&&path.resolve(process.argv[1])===fileURLToPath(import.meta.url)){
|
|
53
|
+
const [packageRoot,generationRoot]=process.argv.slice(2);
|
|
54
|
+
if(!packageRoot||!generationRoot) throw new Error('usage: node src/r36-full-surface-probe.mjs <package-root> <generation-root>');
|
|
55
|
+
probeR36FullSurface({packageRoot,generationRoot}).then(v=>console.log(JSON.stringify(v,null,2))).catch(e=>{console.error(e instanceof Error?e.stack:String(e));process.exitCode=1;});
|
|
56
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
const SAFE_ARG_KEYS=Object.freeze(['root_id','path','pattern','command_id','profile_id','session_id','goal_id','task_id','step_id','mode']);
|
|
2
|
+
const LIFECYCLE=Object.freeze({
|
|
3
|
+
device_start:'🚀 [DEVICE] Starting DEADBYTE MCP...',
|
|
4
|
+
bridge_connected:'🔌 [BRIDGE] Local bridge connected',
|
|
5
|
+
cloud_connected:'🔌 [CLOUD] Remote MCP connected',
|
|
6
|
+
session_new:'[SESSION] New foreground session',
|
|
7
|
+
session_restored:'♻️ [SESSION] Previous result stream replayed',
|
|
8
|
+
ready:'✅ [READY] DEADBYTE MCP online',
|
|
9
|
+
cleanup:'🔐 [AUTH] Revoking session authority...'
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
function safeAtom(value){
|
|
13
|
+
if(typeof value==='string'){
|
|
14
|
+
if(value.length<1||value.length>160||/[\r\n\0]/.test(value)) return null;
|
|
15
|
+
return value;
|
|
16
|
+
}
|
|
17
|
+
if(typeof value==='number'&&Number.isFinite(value)) return String(value);
|
|
18
|
+
if(typeof value==='boolean') return String(value);
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function summarizeArgs(args){
|
|
23
|
+
if(!args||typeof args!=='object'||Array.isArray(args)) return '';
|
|
24
|
+
const parts=[];
|
|
25
|
+
for(const key of SAFE_ARG_KEYS){
|
|
26
|
+
if(!Object.hasOwn(args,key)) continue;
|
|
27
|
+
const value=safeAtom(args[key]);
|
|
28
|
+
if(value!==null) parts.push(`${key.replace(/_id$/,'')}=${value}`);
|
|
29
|
+
}
|
|
30
|
+
return parts.length?` ${parts.join(' ')}`:'';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function formatDuration(ms){
|
|
34
|
+
const value=Number(ms);
|
|
35
|
+
if(!Number.isFinite(value)||value<0) return '';
|
|
36
|
+
if(value<1000) return `${Math.round(value)}ms`;
|
|
37
|
+
return `${(value/1000).toFixed(2)}s`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function formatCompactAuditEvent(event){
|
|
41
|
+
if(!event||typeof event!=='object'||Array.isArray(event)||typeof event.tool!=='string'||!event.tool) return null;
|
|
42
|
+
if(event.event_type==='tool.request') return `▶ RUN ${event.tool}${summarizeArgs(event.args)}`;
|
|
43
|
+
if(event.event_type==='tool.result'){
|
|
44
|
+
const marker=event.outcome?.is_error===true?'❌ ERR':'✅ OK';
|
|
45
|
+
const duration=formatDuration(event.outcome?.duration_ms);
|
|
46
|
+
return `${marker} ${event.tool}${duration?` ${duration}`:''}`;
|
|
47
|
+
}
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function formatRemoteLifecycle(kind){
|
|
52
|
+
if(typeof kind!=='string'||!Object.hasOwn(LIFECYCLE,kind)) throw new Error(`unknown remote lifecycle '${String(kind)}'`);
|
|
53
|
+
return LIFECYCLE[kind];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function createLiveActivityTracker(){
|
|
57
|
+
const pending=new Map();
|
|
58
|
+
return Object.freeze({
|
|
59
|
+
observe(event){
|
|
60
|
+
if(!event||typeof event!=='object'||Array.isArray(event)) return null;
|
|
61
|
+
if(event.event_type==='tool.request'&&typeof event.request_id==='string'&&event.request_id&&typeof event.tool==='string'&&event.tool){
|
|
62
|
+
pending.set(event.request_id,{request_id:event.request_id,tool:event.tool});
|
|
63
|
+
}else if(event.event_type==='tool.result'&&typeof event.request_id==='string'&&event.request_id){
|
|
64
|
+
pending.delete(event.request_id);
|
|
65
|
+
}
|
|
66
|
+
return formatCompactAuditEvent(event);
|
|
67
|
+
},
|
|
68
|
+
pending(){return [...pending.values()].map(item=>({...item}));},
|
|
69
|
+
reconcileInterrupted(reason='session_end'){
|
|
70
|
+
const suffix=reason==='session_end'?'session ended before result':reason==='recovery'?'session recovered before result':'result not observed before stream ended';
|
|
71
|
+
const lines=[...pending.values()].map(item=>`⚠️ INTERRUPTED ${item.tool} — ${suffix}`);
|
|
72
|
+
pending.clear();
|
|
73
|
+
return lines;
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
}
|
|
@@ -3,7 +3,7 @@ import { readFile } from 'node:fs/promises';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
5
|
import { Client, StreamableHTTPClientTransport } from '@modelcontextprotocol/client';
|
|
6
|
-
import {
|
|
6
|
+
import { R36_COMPACT_GATEWAYS, R36_COMPACT_TOOL_LIMIT } from './remote-mcp-r36-parity-client.mjs';
|
|
7
7
|
import { promptContexts, promptObservations, promptMutationEpoch } from './autonomous-live-state.mjs';
|
|
8
8
|
|
|
9
9
|
const endpointText = process.argv[2];
|
|
@@ -96,7 +96,7 @@ function plannerDecision(prompt) {
|
|
|
96
96
|
return { schema:'deadbyte.autonomous-decision.v1', action:'inspect', summary:'gather current fixture state before deciding next action',
|
|
97
97
|
reads:[{ path:fixtureRelative, max_bytes:4096 }] };
|
|
98
98
|
}const client = new Client(
|
|
99
|
-
{ name:'deadbyte-
|
|
99
|
+
{ name:'deadbyte-r36-public-autonomous-gate', version:'0.10.0' },
|
|
100
100
|
{ capabilities:{ sampling:{} }, versionNegotiation:{ mode:{ pin:'2026-07-28' } },
|
|
101
101
|
inputRequired:{ autoFulfill:true, maxRounds:16 } }
|
|
102
102
|
);
|
|
@@ -113,7 +113,11 @@ try {
|
|
|
113
113
|
await client.connect(transport);
|
|
114
114
|
const listed = await client.listTools();
|
|
115
115
|
const names = listed.tools.map(tool => tool.name).sort();
|
|
116
|
-
if (
|
|
116
|
+
if (names.length > R36_COMPACT_TOOL_LIMIT) throw new Error(`R36 compact public catalog exceeds ${R36_COMPACT_TOOL_LIMIT}: ${names.length}`);
|
|
117
|
+
for (const name of R36_COMPACT_GATEWAYS) if (!names.includes(name)) throw new Error(`R36 compact public gateway missing ${name}`);
|
|
118
|
+
for (const name of ['autonomous_goal_submit','autonomous_goal_run','autonomous_goal_status','autonomous_goal_events','autonomous_goal_verify','autonomous_release_gate','autonomous_session_status','native_diff_apply','native_diff_preview','native_diff_verify']) {
|
|
119
|
+
if (!names.includes(name)) throw new Error(`R36 compact autonomous control missing ${name}`);
|
|
120
|
+
}
|
|
117
121
|
for (const tool of listed.tools) {
|
|
118
122
|
if (tool.outputSchema?.type !== 'object' || tool.outputSchema?.additionalProperties !== false || !tool.outputSchema?.properties) {
|
|
119
123
|
throw new Error(`tool ${tool.name} missing strict output schema`);
|
|
@@ -19,7 +19,7 @@ const taskPath = `workspaces/demo/.deadbyte-coding-task-${tag}.txt`;
|
|
|
19
19
|
const codingTools = [
|
|
20
20
|
'coding_benchmark_run', 'coding_command_run', 'coding_file_read', 'coding_file_write', 'coding_files_read',
|
|
21
21
|
'coding_receipt_verify', 'coding_search', 'coding_session_status', 'coding_stat',
|
|
22
|
-
'coding_symbol_definition', 'coding_symbol_outline', 'coding_symbol_references', 'coding_symbol_replace',
|
|
22
|
+
'coding_symbol_definition', 'coding_symbol_diagnostics', 'coding_symbol_implementations', 'coding_symbol_outline', 'coding_symbol_references', 'coding_symbol_rename_preview', 'coding_symbol_replace',
|
|
23
23
|
'coding_task_approve', 'coding_task_cancel', 'coding_task_events', 'coding_task_run', 'coding_task_status',
|
|
24
24
|
'coding_task_submit', 'coding_task_verify', 'coding_text_replace', 'coding_tree'
|
|
25
25
|
];
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { Client, StreamableHTTPClientTransport } from '@modelcontextprotocol/client';
|
|
7
|
+
import { R34_EXPECTED_TOOLS } from './remote-mcp-r34-parity-client.mjs';
|
|
8
|
+
import { loadCodingPolicy } from './coding-plane.mjs';
|
|
9
|
+
import { verifyMachineFsReceiptFile } from './machine-fs.mjs';
|
|
10
|
+
import { verifyObservationReceiptFile } from './observation-runtime.mjs';
|
|
11
|
+
|
|
12
|
+
export const R36_EXPECTED_TOOLS=Object.freeze([...new Set([
|
|
13
|
+
...R34_EXPECTED_TOOLS,
|
|
14
|
+
'coding_symbol_diagnostics','coding_symbol_implementations','coding_symbol_rename_preview'
|
|
15
|
+
])].sort());
|
|
16
|
+
if(R36_EXPECTED_TOOLS.length !== 76) throw new Error(`R36 full catalog identity mismatch: ${R36_EXPECTED_TOOLS.length}`);
|
|
17
|
+
export const R36_COMPACT_GATEWAYS=Object.freeze(['capability_manifest','coding_inspect','machine_fs','machine_observe','process_session'].sort());
|
|
18
|
+
export const R36_COMPACT_TOOL_LIMIT=42;
|
|
19
|
+
|
|
20
|
+
const here=path.dirname(fileURLToPath(import.meta.url));
|
|
21
|
+
const repoRoot=path.resolve(here,'..');
|
|
22
|
+
const stateRoot=path.resolve(process.env.DEADBYTE_STATE_ROOT||path.join(os.homedir(),'.deadbyte-mcp'));
|
|
23
|
+
const verifyKeyPath=path.join(stateRoot,'trust','ed25519-public.pem');
|
|
24
|
+
const sha256=bytes=>createHash('sha256').update(bytes).digest('hex');
|
|
25
|
+
const sleep=ms=>new Promise(resolve=>setTimeout(resolve,ms));
|
|
26
|
+
|
|
27
|
+
function structured(result,label){
|
|
28
|
+
if(result.isError) throw new Error(`${label} returned error: ${JSON.stringify(result.structuredContent??result.content)}`);
|
|
29
|
+
if(!result.structuredContent||typeof result.structuredContent!=='object') throw new Error(`${label} missing structured output`);
|
|
30
|
+
return result.structuredContent;
|
|
31
|
+
}
|
|
32
|
+
function gateway(result,label){
|
|
33
|
+
const value=structured(result,label);
|
|
34
|
+
if(value.status!=='ok'||!value.result||typeof value.result!=='object') throw new Error(`${label} gateway status=${String(value.status)} error=${String(value.error??'')}`);
|
|
35
|
+
return value.result;
|
|
36
|
+
}
|
|
37
|
+
async function fileSha(relative){return sha256(await readFile(path.join(repoRoot,...relative.split('/'))));}
|
|
38
|
+
async function verifyMachineReceipt(policy,receiptId,operation){
|
|
39
|
+
const checked=await verifyMachineFsReceiptFile({policy,verifyKeyPath,receiptId});
|
|
40
|
+
if(!checked.ok||checked.signature_valid!==true||checked.operation!==operation) throw new Error(`machine-fs receipt verification failed for ${operation}`);
|
|
41
|
+
return checked;
|
|
42
|
+
}
|
|
43
|
+
async function verifyObservationReceipt(policy,receiptId){
|
|
44
|
+
const checked=await verifyObservationReceiptFile({policy,verifyKeyPath,receiptId});
|
|
45
|
+
if(!checked.ok||checked.signature_valid!==true) throw new Error('observation receipt verification failed');
|
|
46
|
+
return checked;
|
|
47
|
+
}
|
|
48
|
+
async function waitSearch(client,searchId){
|
|
49
|
+
for(let attempt=0;attempt<200;attempt+=1){
|
|
50
|
+
const status=gateway(await client.callTool({name:'machine_observe',arguments:{op:'search_status',search_id:searchId}}),'search_status');
|
|
51
|
+
if(['completed','cancelled','failed'].includes(status.state)) return status;
|
|
52
|
+
await sleep(25);
|
|
53
|
+
}
|
|
54
|
+
throw new Error('R36 compact search timeout');
|
|
55
|
+
}
|
|
56
|
+
async function waitProcessOutput(client,sessionId){
|
|
57
|
+
for(let attempt=0;attempt<100;attempt+=1){
|
|
58
|
+
const page=gateway(await client.callTool({name:'process_session',arguments:{op:'read',session_id:sessionId,offset:0,max_bytes:65536}}),'process read');
|
|
59
|
+
if(Buffer.from(page.data_base64??'','base64').toString('utf8').includes('DEADBYTE_ACTION_PING')) return page;
|
|
60
|
+
await sleep(50);
|
|
61
|
+
}
|
|
62
|
+
throw new Error('R36 compact process output timeout');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export async function runR36CompactParity(endpointText){
|
|
66
|
+
const endpoint=new URL(endpointText);
|
|
67
|
+
if(endpoint.protocol!=='https:') throw new Error('R36 compact parity requires HTTPS');
|
|
68
|
+
const pointer=JSON.parse(await readFile(path.join(stateRoot,'active-release.json'),'utf8'));
|
|
69
|
+
const policy=await loadCodingPolicy(path.join(pointer.generation_root,'coding-policy.json'));
|
|
70
|
+
const client=new Client({name:'deadbyte-r36-compact-parity',version:'0.10.0'},{versionNegotiation:{mode:{pin:'2026-07-28'}}});
|
|
71
|
+
const transport=new StreamableHTTPClientTransport(endpoint);
|
|
72
|
+
const tag=randomBytes(8).toString('hex');
|
|
73
|
+
const scratchRoot='parity_scratch';
|
|
74
|
+
const target=`.deadbyte-r36-parity-${tag}.txt`;
|
|
75
|
+
let processSession=null,targetPresent=false;
|
|
76
|
+
const machineReceipts=[],observationReceipts=[];
|
|
77
|
+
try{
|
|
78
|
+
await client.connect(transport);
|
|
79
|
+
const listed=await client.listTools();
|
|
80
|
+
const names=listed.tools.map(tool=>tool.name).sort();
|
|
81
|
+
if(names.length>R36_COMPACT_TOOL_LIMIT) throw new Error(`R36 compact catalog exceeds ${R36_COMPACT_TOOL_LIMIT}: ${names.length}`);
|
|
82
|
+
for(const name of R36_COMPACT_GATEWAYS) if(!names.includes(name)) throw new Error(`R36 compact gateway missing ${name}`);
|
|
83
|
+
for(const tool of listed.tools){
|
|
84
|
+
if(tool.outputSchema?.type!=='object'||tool.outputSchema?.additionalProperties!==false||!tool.outputSchema?.properties) throw new Error(`tool ${tool.name} missing strict output schema`);
|
|
85
|
+
}
|
|
86
|
+
const manifest=gateway(await client.callTool({name:'capability_manifest',arguments:{}}),'capability_manifest');
|
|
87
|
+
if(manifest.schema!=='deadbyte.capability-manifest.v1'||manifest.profile!=='compact'||!Array.isArray(manifest.capabilities)||!/^[0-9a-f]{64}$/.test(String(manifest.ledger_sha256??''))) throw new Error('R36 compact capability manifest malformed');
|
|
88
|
+
const required=manifest.capabilities.filter(item=>item?.production===true&&item?.host_only!==true);
|
|
89
|
+
for(const item of required){
|
|
90
|
+
if(typeof item.compact_tool!=='string'||!names.includes(item.compact_tool)) throw new Error(`compact capability route missing: ${String(item.id)}`);
|
|
91
|
+
}
|
|
92
|
+
const coding=structured(await client.callTool({name:'coding_session_status',arguments:{}}),'coding_session_status');
|
|
93
|
+
const autonomous=structured(await client.callTool({name:'autonomous_session_status',arguments:{}}),'autonomous_session_status');
|
|
94
|
+
if(coding.armed!==true||autonomous.coding_armed!==true||autonomous.autonomous_armed!==true) throw new Error('R36 compact parity requires coding and autonomous standing grants');
|
|
95
|
+
const tree=structured(await client.callTool({name:'coding_tree',arguments:{root_id:'core',path:'workspaces/demo',max_depth:2,max_entries:128}}),'coding_tree');
|
|
96
|
+
if(tree.status!=='ok'||!Array.isArray(tree.entries)) throw new Error('coding_tree malformed');
|
|
97
|
+
const stat=gateway(await client.callTool({name:'coding_inspect',arguments:{op:'stat',root_id:'core',path:'package.json',hash:true}}),'coding stat');
|
|
98
|
+
if(stat.type!=='file'||!/^[0-9a-f]{64}$/.test(String(stat.sha256??''))||!/^[0-9a-f]{64}$/.test(String(stat.observation_sha256??''))) throw new Error('coding stat identity missing');
|
|
99
|
+
const search=gateway(await client.callTool({name:'coding_inspect',arguments:{op:'search',root_id:'core',path:'src',pattern:'R36_CAPABILITY_LEDGER',target:'content',mode:'literal',case_sensitive:true,max_results:20,max_file_bytes:262144,max_scan_bytes:4194304,page_size:20,cursor:null}}),'coding search');
|
|
100
|
+
if(!Array.isArray(search.matches)||search.matches.length<1) throw new Error('coding search returned no R36 ledger match');
|
|
101
|
+
|
|
102
|
+
const buildScript='scripts/autonomous-build.ps1';
|
|
103
|
+
const testScript='scripts/release-parity-tests.ps1';
|
|
104
|
+
const build=structured(await client.callTool({name:'coding_command_run',arguments:{root_id:'core',command_id:'powershell_script',script_path:buildScript,script_sha256:await fileSha(buildScript),args:[],cwd:'.',timeout_ms:120000}},{timeout:180000,maxTotalTimeout:180000}),'coding build');
|
|
105
|
+
if(build.status!=='passed'||build.exit_code!==0) throw new Error('R36 coding build failed');
|
|
106
|
+
const buildVerify=structured(await client.callTool({name:'coding_receipt_verify',arguments:{receipt_id:build.receipt_id}}),'coding build receipt');
|
|
107
|
+
if(buildVerify.ok!==true||buildVerify.signature_valid!==true) throw new Error('R36 coding build receipt invalid');
|
|
108
|
+
const tests=structured(await client.callTool({name:'coding_command_run',arguments:{root_id:'core',command_id:'powershell_script',script_path:testScript,script_sha256:await fileSha(testScript),args:[],cwd:'.',timeout_ms:300000}},{timeout:360000,maxTotalTimeout:360000}),'coding tests');
|
|
109
|
+
if(tests.status!=='passed'||tests.exit_code!==0) throw new Error('R36 coding tests failed');
|
|
110
|
+
const testVerify=structured(await client.callTool({name:'coding_receipt_verify',arguments:{receipt_id:tests.receipt_id}}),'coding test receipt');
|
|
111
|
+
if(testVerify.ok!==true||testVerify.signature_valid!==true) throw new Error('R36 coding test receipt invalid');
|
|
112
|
+
|
|
113
|
+
const initial=`needle-${tag}\n`;
|
|
114
|
+
const write=gateway(await client.callTool({name:'machine_fs',arguments:{op:'write',root_id:scratchRoot,path:target,content:initial,encoding:'utf8',expected_sha256:'absent'}}),'machine_fs write');
|
|
115
|
+
targetPresent=true;machineReceipts.push(write.receipt_id);await verifyMachineReceipt(policy,write.receipt_id,'machine_fs_write');
|
|
116
|
+
const bounded=gateway(await client.callTool({name:'machine_fs',arguments:{op:'read',root_id:scratchRoot,path:target,offset:0,max_bytes:128,hash_file:true}}),'machine_fs read');
|
|
117
|
+
if(Buffer.from(bounded.data_base64,'base64').toString('utf8')!==initial||bounded.file_sha256!==write.sha256) throw new Error('R36 bounded machine read mismatch');
|
|
118
|
+
const edit=gateway(await client.callTool({name:'machine_fs',arguments:{op:'edit_exact',root_id:scratchRoot,path:target,expected_sha256:write.sha256,old_string:'needle',new_string:'beta',expected_replacements:1}}),'machine_fs edit');
|
|
119
|
+
machineReceipts.push(edit.receipt_id);await verifyMachineReceipt(policy,edit.receipt_id,'machine_fs_edit_exact');
|
|
120
|
+
const stale=await client.callTool({name:'machine_fs',arguments:{op:'edit_exact',root_id:scratchRoot,path:target,expected_sha256:write.sha256,old_string:'beta',new_string:'bad',expected_replacements:1}});
|
|
121
|
+
const staleValue=stale.structuredContent;
|
|
122
|
+
if(!staleValue||typeof staleValue!=='object') throw new Error('R36 stale machine-fs denial missing structured output');
|
|
123
|
+
if(stale.isError!==true||staleValue.status!=='denied'||!/precondition mismatch/i.test(String(staleValue.error??''))) {
|
|
124
|
+
throw new Error(`R36 stale machine-fs precondition was not denied: ${JSON.stringify(staleValue)}`);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const searchStart=gateway(await client.callTool({name:'machine_observe',arguments:{op:'search_start',root_id:scratchRoot,path:'.',pattern:tag,case_sensitive:true,max_results:20,max_files:200,max_bytes:262144,max_file_bytes:16384}}),'observe search start');
|
|
128
|
+
const searchStatus=await waitSearch(client,searchStart.search_id);
|
|
129
|
+
if(searchStatus.state!=='completed'||!searchStatus.receipt_id) throw new Error('R36 compact observation search failed');
|
|
130
|
+
observationReceipts.push(searchStatus.receipt_id);await verifyObservationReceipt(policy,searchStatus.receipt_id);
|
|
131
|
+
const page=gateway(await client.callTool({name:'machine_observe',arguments:{op:'search_page',search_id:searchStart.search_id,limit:20,cursor:null}}),'observe search page');
|
|
132
|
+
if(!page.results.some(item=>item.path===target)) throw new Error('R36 compact search did not return parity file');
|
|
133
|
+
const system=gateway(await client.callTool({name:'machine_observe',arguments:{op:'system_status'}}),'system status');
|
|
134
|
+
observationReceipts.push(system.receipt_id);await verifyObservationReceipt(policy,system.receipt_id);
|
|
135
|
+
const machineStatus=gateway(await client.callTool({name:'machine_observe',arguments:{op:'machine_status'}}),'machine status');
|
|
136
|
+
if(machineStatus.release_id!==pointer.release_id||machineStatus.release_manifest_sha256!==pointer.manifest_sha256) throw new Error('R36 compact machine status release mismatch');
|
|
137
|
+
|
|
138
|
+
const started=gateway(await client.callTool({name:'process_session',arguments:{op:'start',profile_id:'ping_hold',root_id:'core',cwd:'.',args:[]}}),'process start');
|
|
139
|
+
processSession=started.session_id??started.session?.session_id;
|
|
140
|
+
if(!/^[0-9a-f]{64}$/.test(String(processSession??''))) throw new Error('R36 compact process session id missing');
|
|
141
|
+
const action=gateway(await client.callTool({name:'process_session',arguments:{op:'action',session_id:processSession,action_id:'ping'}}),'process action');
|
|
142
|
+
const processRead=await waitProcessOutput(client,processSession);
|
|
143
|
+
const terminated=gateway(await client.callTool({name:'process_session',arguments:{op:'terminate',session_id:processSession}}),'process terminate');
|
|
144
|
+
const terminalState=terminated.state??terminated.session?.state;
|
|
145
|
+
if(action.action_id!=='ping'||terminalState!=='terminated') throw new Error('R36 compact process lifecycle failed');
|
|
146
|
+
|
|
147
|
+
const fsStat=gateway(await client.callTool({name:'machine_fs',arguments:{op:'stat',root_id:scratchRoot,path:target,hash:true}}),'machine_fs stat');
|
|
148
|
+
const deleted=gateway(await client.callTool({name:'machine_fs',arguments:{op:'delete',root_id:scratchRoot,path:target,expected_source_identity:fsStat.identity_sha256}}),'machine_fs delete');
|
|
149
|
+
machineReceipts.push(deleted.receipt_id);await verifyMachineReceipt(policy,deleted.receipt_id,'machine_fs_delete');targetPresent=false;
|
|
150
|
+
|
|
151
|
+
return {status:'passed',protocol_revision:client.getNegotiatedProtocolVersion(),profile:'compact',tool_count:names.length,tools:names,
|
|
152
|
+
catalog_sha256:sha256(Buffer.from(JSON.stringify(names),'utf8')),strict_output_schemas:true,ledger_sha256:manifest.ledger_sha256,
|
|
153
|
+
capability_count:manifest.capabilities.length,production_non_host_capabilities:required.length,capability_closure:true,
|
|
154
|
+
gateway_count:R36_COMPACT_GATEWAYS.length,tree_entries:tree.entries.length,coding_stat_verified:true,coding_search_result_count:search.matches.length,
|
|
155
|
+
machine_fs_receipts_verified:machineReceipts.length,observation_receipts_verified:observationReceipts.length,
|
|
156
|
+
observation_search_result_count:page.results.length,process_session_id:processSession,process_output_bytes:processRead.stream_head,
|
|
157
|
+
process_journal_head_sha256:terminated.journal_head_sha256??terminated.session?.journal_head_sha256,
|
|
158
|
+
build_receipt_id:build.receipt_id,test_receipt_id:tests.receipt_id,coding_receipts_verified:2,
|
|
159
|
+
execution_provider:'deadbyte_mcp',rdc_operation_count:0};
|
|
160
|
+
} finally {
|
|
161
|
+
if(processSession) await client.callTool({name:'process_session',arguments:{op:'terminate',session_id:processSession}}).catch(()=>{});
|
|
162
|
+
if(targetPresent){
|
|
163
|
+
try{const fsStat=gateway(await client.callTool({name:'machine_fs',arguments:{op:'stat',root_id:scratchRoot,path:target,hash:false}}),'cleanup stat');
|
|
164
|
+
if(fsStat.identity_sha256) await client.callTool({name:'machine_fs',arguments:{op:'delete',root_id:scratchRoot,path:target,expected_source_identity:fsStat.identity_sha256}}).catch(()=>{});
|
|
165
|
+
}catch{}
|
|
166
|
+
}
|
|
167
|
+
await client.close().catch(()=>{});
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if(process.argv[1]&&path.resolve(process.argv[1])===fileURLToPath(import.meta.url)){
|
|
172
|
+
const endpoint=process.argv[2];
|
|
173
|
+
if(!endpoint) throw new Error('usage: node src/remote-mcp-r36-parity-client.mjs https://host/mcp/path');
|
|
174
|
+
runR36CompactParity(endpoint).then(result=>console.log(JSON.stringify(result,null,2))).catch(error=>{console.error(error instanceof Error?error.stack:String(error));process.exitCode=1;});
|
|
175
|
+
}
|