deadbyte-mcp 0.15.0 → 0.15.2

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 (110) hide show
  1. package/MANIFEST.SHA256 +109 -64
  2. package/PROVENANCE.json +11 -0
  3. package/R45-PARENT.json +18 -0
  4. package/README.txt +36 -20
  5. package/RELEASE.json +53 -0
  6. package/SBOM.cdx.json +46 -0
  7. package/TEST-MATRIX.json +31 -0
  8. package/bin/deadbyte-hardware-observer-r45.exe +0 -0
  9. package/bin/deadbyte-hardware-observer-r45.obj +0 -0
  10. package/controller/README.TXT +2 -2
  11. package/controller/deadbyte-controller.ps1 +51 -3
  12. package/controller/deadbyte-process-policy.json +63 -1
  13. package/docs/ARCHITECTURE.md +3 -4
  14. package/native/deadbyte-hardware-observer.cpp +308 -0
  15. package/package.json +14 -7
  16. package/scripts/autonomous-tests.ps1 +2 -2
  17. package/scripts/build-hardware-r45.ps1 +71 -0
  18. package/scripts/deferred-slot-operation-r45.mjs +126 -0
  19. package/scripts/deferred-slot-operation-r46.mjs +126 -0
  20. package/scripts/derive-r45-parent.mjs +24 -0
  21. package/scripts/derive-r45-release.mjs +72 -0
  22. package/scripts/derive-r45-tests.mjs +20 -0
  23. package/scripts/final-closure-r45.mjs +121 -0
  24. package/scripts/final-closure-r46.mjs +98 -0
  25. package/scripts/final-closure-verify-r45.mjs +220 -0
  26. package/scripts/final-closure-verify-r46.mjs +119 -0
  27. package/scripts/finalize-r45.mjs +219 -0
  28. package/scripts/full-regression-r45.mjs +20 -0
  29. package/scripts/full-regression-r46.mjs +20 -0
  30. package/scripts/full-regression-summary-r45.mjs +14 -0
  31. package/scripts/gate-windows-r45.ps1 +44 -0
  32. package/scripts/gate-windows-r46.ps1 +56 -0
  33. package/scripts/init-host-policy.ps1 +39 -1
  34. package/scripts/inspect-active-release-r45.mjs +20 -0
  35. package/scripts/refresh-process-policy.mjs +64 -7
  36. package/scripts/release-manifest.mjs +3 -1
  37. package/scripts/release-parity-r45.mjs +253 -0
  38. package/scripts/release-parity-r46.mjs +187 -0
  39. package/scripts/release-parity-tests-r45.ps1 +8 -0
  40. package/scripts/verify-r45-parent.mjs +142 -0
  41. package/scripts/windows-gate-evidence-r45.mjs +60 -0
  42. package/scripts/windows-gate-evidence-r46.mjs +110 -0
  43. package/src/autonomous-runtime.mjs +12 -7
  44. package/src/capability-ledger.mjs +2 -0
  45. package/src/compact-gateway-mcp-tools.mjs +11 -7
  46. package/src/deadbyte-cli.mjs +24 -12
  47. package/src/doctor-r45.mjs +65 -0
  48. package/src/host-plane.mjs +133 -85
  49. package/src/machine-fs-mcp-tools.mjs +3 -3
  50. package/src/machine-fs-output-contracts.mjs +1 -1
  51. package/src/machine-fs-read-worker.mjs +46 -0
  52. package/src/machine-fs.mjs +63 -10
  53. package/src/mcp-server.mjs +68 -23
  54. package/src/mcp-smoke-client.mjs +4 -1
  55. package/src/native-diff-v2.mjs +9 -4
  56. package/src/native-diff.mjs +29 -7
  57. package/src/observation-mcp-tools.mjs +5 -1
  58. package/src/observation-output-contracts.mjs +8 -0
  59. package/src/observation-runtime.mjs +57 -5
  60. package/src/observation-smoke-client.mjs +6 -2
  61. package/src/output-contracts.mjs +27 -11
  62. package/src/process-mcp-tools.mjs +18 -5
  63. package/src/process-output-contracts.mjs +20 -3
  64. package/src/process-policy.mjs +3 -1
  65. package/src/process-runtime.mjs +99 -19
  66. package/src/process-smoke-client.mjs +67 -22
  67. package/src/r45-compact-disarmed-probe.mjs +74 -0
  68. package/src/r45-full-surface-probe.mjs +34 -0
  69. package/src/release-generation.mjs +23 -3
  70. package/src/remote-device-r44.mjs +53 -15
  71. package/src/remote-mcp-r45-parity-client.mjs +206 -0
  72. package/src/remote-mcp-smoke-client.mjs +1 -1
  73. package/src/runtime-update.mjs +36 -8
  74. package/src/stdio-console-guard.mjs +24 -0
  75. package/src/version.mjs +1 -1
  76. package/test/agent-runtime.test.mjs +1 -0
  77. package/test/autonomous-loop.test.mjs +24 -0
  78. package/test/autonomous-runtime.test.mjs +1 -0
  79. package/test/cli-entrypoint.test.mjs +4 -4
  80. package/test/coding-plane.test.mjs +1 -0
  81. package/test/compact-gateway-r36.test.mjs +28 -0
  82. package/test/compact-max-catalog-r45.test.mjs +99 -0
  83. package/test/contained.test.mjs +23 -11
  84. package/test/controller.test.mjs +5 -1
  85. package/test/core.test.mjs +1 -1
  86. package/test/full-max-catalog-r45.test.mjs +91 -0
  87. package/test/full-max-catalog-r46.test.mjs +92 -0
  88. package/test/hardware-observation-r45.test.mjs +44 -0
  89. package/test/host-inline-r45.test.mjs +67 -0
  90. package/test/machine-fs-mcp-tools.test.mjs +4 -1
  91. package/test/machine-fs-runtime.test.mjs +18 -2
  92. package/test/machine-mcp-integration.test.mjs +1 -0
  93. package/test/observation-mcp-tools.test.mjs +4 -3
  94. package/test/observation-runtime.test.mjs +29 -2
  95. package/test/output-schema.test.mjs +1 -0
  96. package/test/package-boundary.test.mjs +1 -1
  97. package/test/process-mcp-tools.test.mjs +23 -5
  98. package/test/process-policy-refresh.test.mjs +87 -10
  99. package/test/process-runtime.test.mjs +111 -1
  100. package/test/process-supervision.test.mjs +4 -2
  101. package/test/production-docs.test.mjs +3 -3
  102. package/test/r33-closeout-regression.test.mjs +20 -19
  103. package/test/r33-finalization.test.mjs +3 -3
  104. package/test/r45-autonomous-quick-test.test.mjs +10 -0
  105. package/test/r45-finalization.test.mjs +84 -0
  106. package/test/release-generation.test.mjs +26 -11
  107. package/test/release-version.test.mjs +112 -46
  108. package/test/remote-device-r44.test.mjs +75 -0
  109. package/test/remote-r24-catalog.test.mjs +2 -2
  110. package/test/runtime-host-bootstrap-r45.test.mjs +46 -0
@@ -1,7 +1,9 @@
1
1
  import { createHash, randomBytes, sign as cryptoSign, verify as cryptoVerify } from 'node:crypto';
2
2
  import { createReadStream } from 'node:fs';
3
+ import { spawn } from 'node:child_process';
3
4
  import { lstat, mkdir as fsMkdir, open, readFile, readdir, realpath, rename, rm, rmdir } from 'node:fs/promises';
4
5
  import path from 'node:path';
6
+ import { fileURLToPath } from 'node:url';
5
7
  import { canonicalJson } from './canonical-json.mjs';
6
8
  import { assertCodingArmed, recheckCodingAuthority } from './coding-plane.mjs';
7
9
  import { isInside, validatePortableRelativePath } from './path-policy.mjs';
@@ -29,6 +31,55 @@ async function sha256File(file) {
29
31
  return hash.digest('hex');
30
32
  }
31
33
 
34
+ const machineFsReadWorkerPath = fileURLToPath(new URL('./machine-fs-read-worker.mjs', import.meta.url));
35
+ async function boundedReadWorker({ file, offset, length, hashFile, timeoutMs, workerPath = machineFsReadWorkerPath }) {
36
+ return await new Promise((resolve, reject) => {
37
+ const child = spawn(process.execPath, [workerPath, file, String(offset), String(length), hashFile ? '1' : '0'], {
38
+ windowsHide: true, shell: false, stdio: ['ignore','pipe','pipe']
39
+ });
40
+ const stdout = [];
41
+ let stdoutBytes = 0;
42
+ let stderr = '';
43
+ let settled = false;
44
+ let timedOut = false;
45
+ const finish = (fn, value) => {
46
+ if (settled) return;
47
+ settled = true;
48
+ clearTimeout(timer);
49
+ fn(value);
50
+ };
51
+ child.stdout.on('data', chunk => {
52
+ stdoutBytes += chunk.length;
53
+ if (stdoutBytes > 8 * 1024 * 1024) {
54
+ child.kill();
55
+ finish(reject, new Error('machine fs read worker output exceeded bound'));
56
+ return;
57
+ }
58
+ stdout.push(Buffer.from(chunk));
59
+ });
60
+ child.stderr.on('data', chunk => { stderr = (stderr + chunk.toString('utf8')).slice(-16384); });
61
+ child.once('error', error => finish(reject, error));
62
+ child.once('close', code => {
63
+ if (settled) return;
64
+ if (timedOut) return finish(reject, new Error(`machine fs read timed out after ${timeoutMs}ms`));
65
+ if (code !== 0) return finish(reject, new Error(`machine fs read worker failed exit=${String(code)}: ${stderr}`));
66
+ let value;
67
+ try { value = JSON.parse(Buffer.concat(stdout).toString('utf8')); }
68
+ catch { return finish(reject, new Error('machine fs read worker returned invalid JSON')); }
69
+ if (value?.schema !== 'deadbyte.machine-fs-read-worker.v1' || !Number.isInteger(value.bytes_read) ||
70
+ typeof value.data_base64 !== 'string' || (value.file_sha256 !== null && !SHA256_RE.test(value.file_sha256 ?? ''))) {
71
+ return finish(reject, new Error('machine fs read worker response invalid'));
72
+ }
73
+ finish(resolve, value);
74
+ });
75
+ const timer = setTimeout(() => {
76
+ timedOut = true;
77
+ child.kill();
78
+ }, timeoutMs);
79
+ timer.unref?.();
80
+ });
81
+ }
82
+
32
83
  function getRoot(policy, rootId, permission) {
33
84
  const root = policy.roots[rootId];
34
85
  if (!root) throw new Error(`machine fs root '${rootId}' is not allowed`);
@@ -245,7 +296,7 @@ async function atomicWrite(target, bytes, expectedSha256, authorityCheck = null)
245
296
  return { before, after };
246
297
  }
247
298
 
248
- export function createMachineFsRuntime({ policy, signingKeyPath, verifyKeyPath }) {
299
+ export function createMachineFsRuntime({ policy, signingKeyPath, verifyKeyPath, readWorkerPath = machineFsReadWorkerPath }) {
249
300
  if (!policy) throw new Error('machine fs runtime requires coding policy');
250
301
  if (!signingKeyPath || !verifyKeyPath) throw new Error('machine fs runtime requires trust key paths');
251
302
 
@@ -292,25 +343,27 @@ export function createMachineFsRuntime({ policy, signingKeyPath, verifyKeyPath }
292
343
  const nextCursor = filtered.length > selected.length && selected.length ? encodeCursor(selected.at(-1).name) : null;
293
344
  return { status: 'ok', root_id: rootId, path: found.relative, entries, next_cursor: nextCursor };
294
345
  }
295
- async function readRange({ rootId, relativePath, offset = 0, maxBytes = 65536, hashFile = false }) {
346
+ async function readRange({ rootId, relativePath, offset = 0, maxBytes = 65536, hashFile = false, timeoutMs = 30000 }) {
296
347
  if (!Number.isInteger(offset) || offset < 0) throw new Error('machine fs read offset must be nonnegative integer');
297
348
  if (!Number.isInteger(maxBytes) || maxBytes < 1 || maxBytes > policy.max_read_bytes) throw new Error(`machine fs max_bytes must be in [1, ${policy.max_read_bytes}]`);
349
+ if (!Number.isInteger(timeoutMs) || timeoutMs < 100 || timeoutMs > 180000) throw new Error('machine fs read timeout_ms must be in [100, 180000]');
298
350
  const grant = await assertCodingArmed(policy, rootId);
299
351
  const found = await resolveExisting(policy, grant, rootId, relativePath, 'read', false);
300
352
  const info = await lstat(found.resolved);
301
353
  if (!info.isFile()) throw new Error('machine fs read target must be a regular file');
302
354
  if (offset > info.size) throw new Error('machine fs read offset exceeds file size');
303
- const handle = await open(found.resolved, 'r');
304
355
  const length = Math.min(maxBytes, info.size - offset);
305
- const buffer = Buffer.alloc(length);
306
- try { if (length) await handle.read(buffer, 0, length, offset); }
307
- finally { await handle.close(); }
308
- const fileSha = hashFile ? await sha256File(found.resolved) : null;
356
+ await recheckCodingAuthority(policy, grant, rootId);
357
+ const worker = await boundedReadWorker({ file: found.resolved, offset, length, hashFile, timeoutMs, workerPath: readWorkerPath });
358
+ const buffer = Buffer.from(worker.data_base64, 'base64');
359
+ if (buffer.length !== worker.bytes_read || buffer.length > length) throw new Error('machine fs read worker byte count mismatch');
360
+ await recheckCodingAuthority(policy, grant, rootId);
309
361
  return {
310
362
  status: 'ok', root_id: rootId, path: found.relative,
311
- offset, next_offset: offset + length, file_size: info.size,
312
- eof: offset + length >= info.size, data_base64: buffer.toString('base64'),
313
- data_sha256: sha256Bytes(buffer), file_sha256: fileSha
363
+ offset, next_offset: offset + buffer.length, file_size: info.size,
364
+ eof: offset + buffer.length >= info.size, data_base64: buffer.toString('base64'),
365
+ data_sha256: sha256Bytes(buffer), file_sha256: worker.file_sha256,
366
+ timeout_ms: timeoutMs
314
367
  };
315
368
  }
316
369
 
@@ -14,8 +14,9 @@ import { defaultPrivateKeyPath, defaultPublicKeyPath, loadPrivateSigningKey, loa
14
14
  import { DEADBYTE_VERSION } from './version.mjs';
15
15
  import { currentIngressAdapterMode, TUNNEL_STDIO_PROBE_COMPAT_MODE } from './ingress-mode.mjs';
16
16
  import { installTunnelStdioProbeCompat } from './tunnel-stdio-compat.mjs';
17
+ import { installStdioConsoleGuard } from './stdio-console-guard.mjs';
17
18
  import { decodeUtf8Strict } from './text-policy.mjs';
18
- import { assertHostArmed, hostExec, hostFileRead, hostFileWrite, loadHostPolicy } from './host-plane.mjs';
19
+ import { assertHostArmed, hostExec, hostExecInline, hostExecNative, hostFileRead, hostFileWrite, loadHostPolicy } from './host-plane.mjs';
19
20
  import { createMachineRuntime } from './machine-identity.mjs';
20
21
  import { registerMachineTools } from './machine-mcp-tools.mjs';
21
22
  import { createAgentRuntime } from './agent-runtime.mjs';
@@ -75,8 +76,9 @@ if (!['0','1'].includes(auditRuntimeMode)) throw new Error('DEADBYTE_AUDIT_RUNTI
75
76
  const runtimeStateRoot = path.resolve(process.env.DEADBYTE_STATE_ROOT || path.join(os.homedir(),'.deadbyte-mcp'));
76
77
  const surfaceProfile = capabilityProfileFromEnv(process.env);
77
78
  const localPrivilegedPolicyPath = path.join(packageRoot, 'controller', 'deadbyte-host-policy.json');
79
+ const privilegedPolicyExplicit = Object.hasOwn(process.env, 'DEADBYTE_PRIVILEGED_POLICY');
78
80
  let privilegedPolicyPath = process.env.DEADBYTE_PRIVILEGED_POLICY ?? '';
79
- if (!privilegedPolicyPath) {
81
+ if (!privilegedPolicyExplicit) {
80
82
  try {
81
83
  await access(localPrivilegedPolicyPath);
82
84
  privilegedPolicyPath = localPrivilegedPolicyPath;
@@ -230,6 +232,9 @@ const skillRoot=path.join(runtimeStateRoot,'skills');
230
232
  await mkdir(skillRoot,{recursive:true});
231
233
  const skillRuntime=await createSkillPlane({root:skillRoot,verifyKeyPath});
232
234
 
235
+ installStdioConsoleGuard();
236
+ if (process.env.DEADBYTE_STDIO_CONSOLE_GUARD_PROBE === '1') console.log('DEADBYTE_STDIO_CONSOLE_GUARD_PROBE');
237
+
233
238
  const ingressAdapterMode = currentIngressAdapterMode();
234
239
  installTunnelStdioProbeCompat({
235
240
  enabled: ingressAdapterMode === TUNNEL_STDIO_PROBE_COMPAT_MODE,
@@ -505,31 +510,71 @@ serveStdio(() => {
505
510
  toolServer.registerTool(
506
511
  'host_exec',
507
512
  {
508
- description: 'Privileged host-plane script execution through a locally allowlisted and SHA-256-pinned executable. No raw shell command string is accepted. Requires a live local arm lease.',
509
- inputSchema: z.strictObject({
510
- command_id: z.string().min(1).max(64),
511
- root_id: z.string().min(1).max(64),
512
- script_path: z.string().min(1).max(512),
513
- script_sha256: z.string().regex(/^[0-9a-f]{64}$/),
514
- args: z.array(z.string().max(2048)).max(64).default([]),
515
- cwd: z.string().min(1).max(512).default('.'),
516
- timeout_ms: z.number().int().min(100).max(300000).nullable().default(null)
517
- }),
513
+ description: 'Privileged Host execution with three bounded modes: legacy hash-pinned script, policy-approved inline argv memory, or absolute hash-pinned native executable argv. shell=false; live Host revocation is enforced.',
514
+ inputSchema: z.union([
515
+ z.strictObject({
516
+ mode: z.literal('script').optional().default('script'),
517
+ command_id: z.string().min(1).max(64),
518
+ root_id: z.string().min(1).max(64),
519
+ script_path: z.string().min(1).max(512),
520
+ script_sha256: z.string().regex(/^[0-9a-f]{64}$/),
521
+ args: z.array(z.string().max(2048)).max(64).default([]),
522
+ cwd: z.string().min(1).max(512).default('.'),
523
+ timeout_ms: z.number().int().min(100).max(300000).nullable().default(null)
524
+ }),
525
+ z.strictObject({
526
+ mode: z.literal('inline'),
527
+ command_id: z.string().min(1).max(64),
528
+ root_id: z.string().min(1).max(64),
529
+ command: z.string().min(1).max(262144),
530
+ cwd: z.string().min(1).max(512).default('.'),
531
+ timeout_ms: z.number().int().min(100).max(300000).nullable().default(null)
532
+ }),
533
+ z.strictObject({
534
+ mode: z.literal('native'),
535
+ root_id: z.string().min(1).max(64),
536
+ executable: z.string().min(1).max(2048),
537
+ executable_sha256: z.string().regex(/^[0-9a-f]{64}$/),
538
+ args: z.array(z.string().max(32768)).max(64).default([]),
539
+ cwd: z.string().min(1).max(512).default('.'),
540
+ timeout_ms: z.number().int().min(100).max(300000).nullable().default(null)
541
+ })
542
+ ]),
518
543
  outputSchema: hostExecOutputSchema
519
544
  },
520
- async ({ command_id, root_id, script_path, script_sha256, args, cwd, timeout_ms }) => {
545
+ async args => {
521
546
  try {
522
- const result = await hostExec(hostPolicy, {
523
- commandId: command_id,
524
- rootId: root_id,
525
- scriptPath: script_path,
526
- scriptSha256: script_sha256,
527
- args,
528
- cwd,
529
- timeoutMs: timeout_ms
530
- });
547
+ let result;
548
+ if (args.mode === 'inline') {
549
+ result = await hostExecInline(hostPolicy, {
550
+ commandId: args.command_id,
551
+ rootId: args.root_id,
552
+ command: args.command,
553
+ cwd: args.cwd,
554
+ timeoutMs: args.timeout_ms
555
+ });
556
+ } else if (args.mode === 'native') {
557
+ result = await hostExecNative(hostPolicy, {
558
+ rootId: args.root_id,
559
+ executable: args.executable,
560
+ executableSha256: args.executable_sha256,
561
+ args: args.args,
562
+ cwd: args.cwd,
563
+ timeoutMs: args.timeout_ms
564
+ });
565
+ } else {
566
+ result = await hostExec(hostPolicy, {
567
+ commandId: args.command_id,
568
+ rootId: args.root_id,
569
+ scriptPath: args.script_path,
570
+ scriptSha256: args.script_sha256,
571
+ args: args.args,
572
+ cwd: args.cwd,
573
+ timeoutMs: args.timeout_ms
574
+ });
575
+ }
531
576
  const failed = result.timed_out || result.exit_code !== 0;
532
- return textResult(hostExecOutputSchema, { status: failed ? 'failed' : 'passed', root_id, ...result }, failed);
577
+ return textResult(hostExecOutputSchema, { status: failed ? 'failed' : 'passed', root_id: args.root_id, ...result }, failed);
533
578
  } catch (error) {
534
579
  return textResult(hostExecOutputSchema, { status: 'denied', error: String(error) }, true);
535
580
  }
@@ -38,7 +38,8 @@ const transport = new StdioClientTransport({
38
38
  ...getDefaultEnvironment(),
39
39
  DEADBYTE_WORKSPACE_ROOT: workspaceRoot,
40
40
  DEADBYTE_SIGNING_KEY: keys.privateKeyPath,
41
- DEADBYTE_VERIFY_KEY: keys.publicKeyPath
41
+ DEADBYTE_VERIFY_KEY: keys.publicKeyPath,
42
+ DEADBYTE_STDIO_CONSOLE_GUARD_PROBE: '1'
42
43
  },
43
44
  stderr: 'pipe'
44
45
  });
@@ -64,6 +65,8 @@ try {
64
65
 
65
66
  const listed = await client.listTools();
66
67
  const names = listed.tools.map(tool => tool.name).sort();
68
+ const guardStderr = Buffer.concat(serverStderr).toString('utf8');
69
+ if (!guardStderr.includes('DEADBYTE MCP LOG DEADBYTE_STDIO_CONSOLE_GUARD_PROBE')) throw new Error('stdio console guard probe did not reach stderr');
67
70
  const baseTools = ['contained_transform', 'evidence_verify', 'file_read', 'proof_execute', 'skill_catalog', 'workspace_open'];
68
71
  const privilegedTools = ['host_exec', 'host_file_read', 'host_file_write'];
69
72
  const allowedCatalogs = [
@@ -321,17 +321,22 @@ export function createNativeDiffV2Runtime({policy,codingRuntime,machineFsRuntime
321
321
  const receipt=await createApplyReceipt(ctx,diffId,identity,preimage,events);return {status:'committed',diff_id:diffId,receipt_sha256:receipt.receipt_sha256,operation_count:identity.operations.length,operation_receipts:receipt.operation_receipts.map(x=>({index:x.index,kind:x.kind,receipt_class:x.receipt_class,receipt_id:x.receipt_id,receipt_sha256:x.receipt_sha256}))};
322
322
  }
323
323
 
324
- async function verify({diffId}){
324
+ async function verifyEvidence({diffId}){
325
325
  try{
326
326
  const {ctx,identity}=await loadDiff(diffId);const verifyKey=await loadPublicVerifyKey(verifyKeyPath);const preimage=await readEnvelope(ctx.preimagePath,verifyKey,PREIMAGE_DOMAIN,'native diff V2 preimage');const events=await readEvents(ctx);const receipt=await readEnvelope(ctx.receiptPath,verifyKey,RECEIPT_DOMAIN,'native diff V2 receipt');const {attestation:_a,receipt_sha256:claimed,...body}=receipt;
327
327
  if(body.schema!==NATIVE_DIFF_V2_RECEIPT_SCHEMA||body.diff_id!==diffId||body.policy_sha256!==policy.policy_sha256||body.coding_policy_sha256!==policy.coding_policy_sha256||!SHA256_RE.test(claimed??'')||sha256Object(body)!==claimed)return {ok:false,reason:'native diff V2 receipt identity/hash mismatch'};
328
328
  const completion=events.at(-1);if(completion?.type!=='transaction_completed'||completion.prev_sha256!==receipt.event_head_sha256||completion.payload?.receipt_sha256!==claimed||receipt.preimage_sha256!==preimage.preimage_sha256)return {ok:false,reason:'native diff V2 receipt/event/preimage binding mismatch'};
329
329
  if(!Array.isArray(receipt.operation_receipts)||receipt.operation_receipts.length!==identity.operations.length)return {ok:false,reason:'native diff V2 operation receipt count mismatch'};
330
330
  let verified=0,codingVerified=0;for(let index=0;index<identity.operations.length;index+=1){const op=identity.operations[index];const bound=receipt.operation_receipts.find(x=>x.index===index);if(!bound||bound.kind!==op.kind||bound.receipt_class!==receiptClass(op))return {ok:false,reason:`native diff V2 receipt binding mismatch index ${index}`};const checked=await verifyOperationReceipt(op,bound.receipt_id);if(!checked.ok||checked.receipt_sha256!==bound.receipt_sha256||checked.request_sha256!==bound.request_sha256)return {ok:false,reason:`native diff V2 operation receipt invalid index ${index}`};verified+=1;if(bound.receipt_class==='coding')codingVerified+=1;}
331
- let currentMatches=true;for(const op of identity.operations)if(!(await terminalStateMatches(identity.root_id,op)))currentMatches=false;
332
- return {ok:true,diff_id:diffId,receipt_sha256:claimed,signature_valid:true,root_id:identity.root_id,operation_count:identity.operations.length,coding_receipts_verified:codingVerified,operation_receipts_verified:verified,final_states:receipt.final_states,current_state_matches:currentMatches,journal_head_sha256:completion.event_sha256};
331
+ return {ok:true,diff_id:diffId,receipt_sha256:claimed,signature_valid:true,root_id:identity.root_id,operation_count:identity.operations.length,coding_receipts_verified:codingVerified,operation_receipts_verified:verified,final_states:receipt.final_states,journal_head_sha256:completion.event_sha256};
333
332
  }catch(error){return {ok:false,reason:String(error).slice(0,2048)};}
334
333
  }
335
334
 
336
- return Object.freeze({preview,apply,verify,policy});
335
+ async function verify({diffId}){
336
+ const checked=await verifyEvidence({diffId});if(!checked.ok)return checked;
337
+ try{const {identity}=await loadDiff(diffId);let currentMatches=true;for(const op of identity.operations)if(!(await terminalStateMatches(identity.root_id,op)))currentMatches=false;return {...checked,current_state_matches:currentMatches};}
338
+ catch(error){return {ok:false,reason:String(error).slice(0,2048)};}
339
+ }
340
+
341
+ return Object.freeze({preview,apply,verify,verifyEvidence,policy});
337
342
  }
@@ -361,7 +361,7 @@ function createNativeDiffRuntimeV1({ policy, codingRuntime, signingKeyPath, veri
361
361
  coding_receipts:receipt.coding_receipts.map(item => ({ index:item.index, path:item.path, receipt_id:item.receipt_id, receipt_sha256:item.receipt_sha256 })) };
362
362
  }
363
363
 
364
- async function verify({ diffId }) {
364
+ async function verifyEvidence({ diffId }) {
365
365
  try {
366
366
  const { ctx, identity } = await loadDiff(diffId);
367
367
  const verifyKey = await loadPublicVerifyKey(verifyKeyPath);
@@ -390,21 +390,32 @@ function createNativeDiffRuntimeV1({ policy, codingRuntime, signingKeyPath, veri
390
390
  }
391
391
  verifiedReceipts += 1;
392
392
  }
393
+ return { ok:true, diff_id:diffId, receipt_sha256:claimed, signature_valid:true,
394
+ root_id:identity.root_id, operation_count:identity.operations.length,
395
+ coding_receipts_verified:verifiedReceipts, final_states:receipt.final_states,
396
+ journal_head_sha256:completion.event_sha256 };
397
+ } catch (error) {
398
+ return { ok:false, reason:String(error).slice(0,2048) };
399
+ }
400
+ }
401
+
402
+ async function verify({ diffId }) {
403
+ const checked = await verifyEvidence({ diffId });
404
+ if (!checked.ok) return checked;
405
+ try {
406
+ const { identity } = await loadDiff(diffId);
393
407
  let currentMatches = true;
394
408
  for (const op of identity.operations) {
395
409
  const current = await codingRuntime.fileState({ rootId:identity.root_id, relativePath:op.path });
396
410
  if (!stateMatches(current, op.after_sha256)) currentMatches = false;
397
411
  }
398
- return { ok:true, diff_id:diffId, receipt_sha256:claimed, signature_valid:true,
399
- root_id:identity.root_id, operation_count:identity.operations.length,
400
- coding_receipts_verified:verifiedReceipts, final_states:receipt.final_states,
401
- current_state_matches:currentMatches, journal_head_sha256:completion.event_sha256 };
412
+ return { ...checked, current_state_matches:currentMatches };
402
413
  } catch (error) {
403
414
  return { ok:false, reason:String(error).slice(0,2048) };
404
415
  }
405
416
  }
406
417
 
407
- return Object.freeze({ preview, apply, verify, policy });
418
+ return Object.freeze({ preview, apply, verify, verifyEvidence, policy });
408
419
  }
409
420
 
410
421
  export { NATIVE_DIFF_V2_SCHEMA };
@@ -448,5 +459,16 @@ export function createNativeDiffRuntime(options) {
448
459
  return {ok:false,reason:String(error).slice(0,2048)};
449
460
  }
450
461
  }
451
- return Object.freeze({preview,apply,verify,policy:options.policy});
462
+ async function verifyEvidence(args){
463
+ try {
464
+ const schema=await schemaFor(args?.diffId);
465
+ if(schema===NATIVE_DIFF_SCHEMA) return v1.verifyEvidence(args);
466
+ if(schema===NATIVE_DIFF_V2_SCHEMA){
467
+ if(!v2) return {ok:false,reason:'native diff V2 topology requires machine fs runtime'};
468
+ return v2.verifyEvidence(args);
469
+ }
470
+ return {ok:false,reason:`unsupported native diff schema: ${String(schema)}`};
471
+ }catch(error){return {ok:false,reason:String(error).slice(0,2048)};}
472
+ }
473
+ return Object.freeze({preview,apply,verify,verifyEvidence,policy:options.policy});
452
474
  }
@@ -1,6 +1,6 @@
1
1
  import * as z from 'zod/v4';
2
2
  import {
3
- observationProcessListOutputSchema,observationSearchCancelOutputSchema,observationSearchPageOutputSchema,
3
+ observationHardwareQueryOutputSchema,observationProcessListOutputSchema,observationSearchCancelOutputSchema,observationSearchPageOutputSchema,
4
4
  observationSearchStartOutputSchema,observationSearchStatusOutputSchema,observationStructuredResult,observationSystemStatusOutputSchema
5
5
  } from './observation-output-contracts.mjs';
6
6
 
@@ -41,4 +41,8 @@ export function registerObservationTools(server,{runtime}){
41
41
  description:'Read bounded timestamped system status with signed observation evidence. This tool cannot mutate or arm any authority.',
42
42
  inputSchema:z.strictObject({}),outputSchema:observationSystemStatusOutputSchema
43
43
  },async()=>{try{return ok(observationSystemStatusOutputSchema,await runtime.systemStatus());}catch(error){return fail(observationSystemStatusOutputSchema,error);}});
44
+ server.registerTool('machine_hardware_query',{
45
+ description:'Read one typed native hardware snapshot (CPU, GPU, memory, storage, network, or PCI) with signed observation evidence. No shell or Host authority is used.',
46
+ inputSchema:z.strictObject({query:z.enum(['cpu','gpu','memory','storage','network','pci'])}),outputSchema:observationHardwareQueryOutputSchema
47
+ },async args=>{try{return ok(observationHardwareQueryOutputSchema,await runtime.hardwareQuery({query:args.query}));}catch(error){return fail(observationHardwareQueryOutputSchema,error);}});
44
48
  }
@@ -45,6 +45,14 @@ export const observationSystemStatusOutputSchema=statusContract({
45
45
  ...Object.fromEntries(Object.entries(evidenceShape).map(([k,v])=>[k,v.optional()]))
46
46
  },['observed_at_utc','hostname','platform','architecture','uptime_seconds','total_memory_bytes','free_memory_bytes','cpu_count','load_average','request_sha256','result_head_sha256','receipt_id','receipt_sha256']);
47
47
 
48
+ export const observationHardwareQueryOutputSchema=statusContract({
49
+ observed_at_utc:z.string().min(1).optional(),
50
+ query:z.enum(['cpu','gpu','memory','storage','network','pci']).optional(),
51
+ data:z.record(z.string(),z.json()).optional(),
52
+ observer_sha256:hex64.optional(),
53
+ ...Object.fromEntries(Object.entries(evidenceShape).map(([k,v])=>[k,v.optional()]))
54
+ },['observed_at_utc','query','data','request_sha256','result_head_sha256','receipt_id','receipt_sha256']);
55
+
48
56
  export function observationStructuredResult(schema,value,isError=false){
49
57
  return structuredToolResult(schema,value,isError);
50
58
  }
@@ -3,6 +3,7 @@ import { spawnSync } from 'node:child_process';
3
3
  import { lstat, mkdir, open, readFile, readdir, realpath } from 'node:fs/promises';
4
4
  import os from 'node:os';
5
5
  import path from 'node:path';
6
+ import { fileURLToPath } from 'node:url';
6
7
  import { canonicalJson } from './canonical-json.mjs';
7
8
  import { decodeUtf8Strict } from './text-policy.mjs';
8
9
  import { isInside, validatePortableRelativePath } from './path-policy.mjs';
@@ -13,6 +14,7 @@ const RECEIPT_DOMAIN=Buffer.from('DEADBYTE-OBSERVATION-RECEIPT-V1\0','utf8');
13
14
  const ZERO='0'.repeat(64);
14
15
  const HEX64=/^[0-9a-f]{64}$/;
15
16
  const ID=/^[A-Za-z0-9._-]{1,64}$/;
17
+ const HARDWARE_QUERIES=new Set(['cpu','gpu','memory','storage','network','pci']);
16
18
  const sha256Bytes=bytes=>createHash('sha256').update(bytes).digest('hex');
17
19
  const sha256Object=value=>sha256Bytes(Buffer.from(canonicalJson(value),'utf8'));
18
20
  const nowUtc=()=>new Date().toISOString();
@@ -110,19 +112,53 @@ function defaultSystemSnapshotProvider(){
110
112
  return {hostname:os.hostname(),platform:process.platform,architecture:process.arch,uptime_seconds:Math.floor(os.uptime()),
111
113
  total_memory_bytes:os.totalmem(),free_memory_bytes:os.freemem(),cpu_count:os.cpus().length,load_average:os.loadavg().map(Number)};
112
114
  }
115
+ async function defaultHardwareSnapshotProvider(query){
116
+ if(process.platform!=='win32') throw new Error('native hardware observation is supported on win32 only');
117
+ if(!HARDWARE_QUERIES.has(query)) throw new Error('unsupported hardware query');
118
+ const executable=fileURLToPath(new URL('../bin/deadbyte-hardware-observer-r45.exe',import.meta.url));
119
+ const executableSha256=sha256Bytes(await readFile(executable));
120
+ const result=spawnSync(executable,[query],{encoding:'utf8',windowsHide:true,shell:false,timeout:15000,maxBuffer:16*1024*1024});
121
+ if(result.error) throw result.error;
122
+ if(result.status!==0) throw new Error(`hardware observation failed query=${query}: ${String(result.stderr??result.stdout??'').trim()}`);
123
+ let parsed;
124
+ try{parsed=JSON.parse(String(result.stdout??'').trim());}
125
+ catch{throw new Error(`hardware observation returned invalid JSON query=${query}`);}
126
+ if(parsed?.schema!=='deadbyte.hardware-query.v1'||parsed?.query!==query||!parsed.data||typeof parsed.data!=='object'||Array.isArray(parsed.data)){
127
+ throw new Error(`hardware observation contract mismatch query=${query}`);
128
+ }
129
+ return {...parsed,observer_sha256:executableSha256};
130
+ }
113
131
  function requestSha(policy,operation,args){
114
132
  return sha256Object({schema:'deadbyte.observation-request.v1',policy_sha256:policy.policy_sha256,operation,args});
115
133
  }
116
134
  export function createObservationRuntime({policy,signingKeyPath,verifyKeyPath,processRuntime=null,stateRoot=path.join(os.homedir(),'.deadbyte-mcp'),
117
135
  processSnapshotProvider=defaultProcessSnapshotProvider,systemSnapshotProvider=defaultSystemSnapshotProvider,
118
- ownedRuntimePidProvider=defaultOwnedRuntimePidProvider}={}){
136
+ ownedRuntimePidProvider=defaultOwnedRuntimePidProvider,hardwareSnapshotProvider=defaultHardwareSnapshotProvider,
137
+ searchSessionTtlMs=5*60*1000,maxSearchSessions=128,clockMs=()=>Date.now()}={}){
119
138
  if(!policy||!signingKeyPath||!verifyKeyPath) throw new Error('observation runtime requires policy and trust keys');
139
+ if(!Number.isInteger(searchSessionTtlMs)||searchSessionTtlMs<10||searchSessionTtlMs>24*60*60*1000) throw new Error('searchSessionTtlMs out of range');
140
+ if(!Number.isInteger(maxSearchSessions)||maxSearchSessions<1||maxSearchSessions>4096) throw new Error('maxSearchSessions out of range');
141
+ if(typeof clockMs!=='function') throw new Error('clockMs must be a function');
120
142
  const searches=new Map(),snapshots=new Map();
121
143
  let signingPromise=null;
122
144
  const runtime={policy,signing:()=>signingPromise??=(loadPrivateSigningKey(signingKeyPath))};
145
+ const terminalSearchState=state=>['completed','cancelled','failed'].includes(state);
146
+ function pruneSearches(){
147
+ const now=Number(clockMs());
148
+ if(!Number.isFinite(now)) throw new Error('search clock invalid');
149
+ for(const [id,session] of searches){
150
+ if(terminalSearchState(session.state)&&Number.isFinite(session.completed_at_ms)&&now-session.completed_at_ms>=searchSessionTtlMs) searches.delete(id);
151
+ }
152
+ if(searches.size>maxSearchSessions){
153
+ const terminal=[...searches.values()].filter(s=>terminalSearchState(s.state))
154
+ .sort((a,b)=>(a.completed_at_ms??Infinity)-(b.completed_at_ms??Infinity)||a.search_id.localeCompare(b.search_id));
155
+ for(const session of terminal){ if(searches.size<=maxSearchSessions) break; searches.delete(session.search_id); }
156
+ }
157
+ }
123
158
  function searchFor(searchId){
124
159
  if(!HEX64.test(searchId??'')) throw new Error('invalid search id');
125
- const session=searches.get(searchId); if(!session) throw new Error(`unknown search '${searchId}'`); return session;
160
+ pruneSearches();
161
+ const session=searches.get(searchId); if(!session) throw new Error(`unknown or expired search '${searchId}'`); return session;
126
162
  }
127
163
  async function finalizeSearch(session,state,error=null){
128
164
  session.error=error?String(error):null;
@@ -131,7 +167,8 @@ export function createObservationRuntime({policy,signingKeyPath,verifyKeyPath,pr
131
167
  bytes_scanned:session.bytes_scanned,truncated:session.truncated,error:session.error
132
168
  });
133
169
  session.receipt_id=receipt.receipt_id; session.receipt_sha256=receipt.receipt_sha256;
134
- session.state=state;
170
+ session.state=state; session.completed_at_ms=Number(clockMs());
171
+ pruneSearches();
135
172
  }
136
173
  async function executeSearch(session){
137
174
  if(session.cancel_requested){ await finalizeSearch(session,'cancelled'); return; }
@@ -182,11 +219,13 @@ export function createObservationRuntime({policy,signingKeyPath,verifyKeyPath,pr
182
219
  if(!Number.isInteger(maxBytes)||maxBytes<1||maxBytes>64*1024*1024) throw new Error('max_bytes out of range');
183
220
  if(!Number.isInteger(maxFileBytes)||maxFileBytes<1||maxFileBytes>4*1024*1024) throw new Error('max_file_bytes out of range');
184
221
  rootFor(policy,rootId); if(relativePath!=='.') validatePortableRelativePath(relativePath);
222
+ pruneSearches();
223
+ if(searches.size>=maxSearchSessions) throw new Error('observation search session limit reached');
185
224
  const args={root_id:rootId,path:relativePath,pattern,case_sensitive:caseSensitive,max_results:maxResults,max_files:maxFiles,max_bytes:maxBytes,max_file_bytes:maxFileBytes};
186
225
  const session={search_id:randomBytes(32).toString('hex'),root_id:rootId,path:relativePath,pattern,case_sensitive:caseSensitive,
187
226
  max_results:maxResults,max_files:maxFiles,max_bytes:maxBytes,max_file_bytes:maxFileBytes,state:'pending',cancel_requested:false,
188
227
  results:[],files_scanned:0,bytes_scanned:0,truncated:false,result_head_sha256:ZERO,error:null,receipt_id:null,receipt_sha256:null,
189
- request_sha256:requestSha(policy,'machine_search_start',args)};
228
+ created_at_ms:Number(clockMs()),completed_at_ms:null,request_sha256:requestSha(policy,'machine_search_start',args)};
190
229
  searches.set(session.search_id,session); setImmediate(()=>{ void executeSearch(session); });
191
230
  return {status:'accepted',search_id:session.search_id,state:session.state,request_sha256:session.request_sha256};
192
231
  }
@@ -253,5 +292,18 @@ export function createObservationRuntime({policy,signingKeyPath,verifyKeyPath,pr
253
292
  const receipt=await writeReceipt(runtime,'machine_system_status',req,head,{observed_at_utc:observedAt});
254
293
  return {...result,request_sha256:req,result_head_sha256:head,...receipt};
255
294
  }
256
- return Object.freeze({policy,searchStart,searchPage,searchStatus,searchCancel,processList,systemStatus});
295
+ async function hardwareQuery({query}={}){
296
+ if(typeof query!=='string'||!HARDWARE_QUERIES.has(query)) throw new Error('hardware query must be one of cpu,gpu,memory,storage,network,pci');
297
+ const observedAt=nowUtc();
298
+ const snapshot=await hardwareSnapshotProvider(query);
299
+ if(!snapshot||snapshot.schema!=='deadbyte.hardware-query.v1'||snapshot.query!==query||!snapshot.data||typeof snapshot.data!=='object'||Array.isArray(snapshot.data)){
300
+ throw new Error('hardware query provider contract mismatch');
301
+ }
302
+ const result={status:'ok',observed_at_utc:observedAt,query,data:snapshot.data};
303
+ if(typeof snapshot.observer_sha256==='string'&&HEX64.test(snapshot.observer_sha256)) result.observer_sha256=snapshot.observer_sha256;
304
+ const req=requestSha(policy,'machine_hardware_query',{query}); const head=sha256Object(result);
305
+ const receipt=await writeReceipt(runtime,'machine_hardware_query',req,head,{observed_at_utc:observedAt,query,observer_sha256:result.observer_sha256??null});
306
+ return {...result,request_sha256:req,result_head_sha256:head,...receipt};
307
+ }
308
+ return Object.freeze({policy,searchStart,searchPage,searchStatus,searchCancel,processList,systemStatus,hardwareQuery});
257
309
  }
@@ -57,7 +57,7 @@ const coding=['coding_benchmark_run','coding_command_run','coding_file_read','co
57
57
  'coding_session_status','coding_stat','coding_symbol_definition','coding_symbol_diagnostics','coding_symbol_implementations','coding_symbol_outline','coding_symbol_references','coding_symbol_rename_preview','coding_symbol_replace',
58
58
  'coding_task_approve','coding_task_cancel','coding_task_events','coding_task_run','coding_task_status',
59
59
  'coding_task_submit','coding_task_verify','coding_text_replace','coding_tree'];
60
- const observation=['machine_process_list','machine_search_cancel','machine_search_page','machine_search_start','machine_search_status','machine_system_status'];
60
+ const observation=['machine_hardware_query','machine_process_list','machine_search_cancel','machine_search_page','machine_search_start','machine_search_status','machine_system_status'];
61
61
  const expected=[...base,...machine,...coding,...observation].sort();
62
62
  try{
63
63
  await client.connect(transport);
@@ -70,6 +70,10 @@ try{
70
70
  if(system.status!=='ok'||!/^[0-9a-f]{24}$/.test(system.receipt_id??'')) throw new Error('system observation missing signed receipt');
71
71
  const systemVerify=await verifyObservationReceiptFile({policy,verifyKeyPath:keys.publicKeyPath,receiptId:system.receipt_id});
72
72
  if(!systemVerify.ok||systemVerify.signature_valid!==true||systemVerify.request_sha256!==system.request_sha256) throw new Error('system observation receipt verification failed');
73
+ const hardware=structured(await client.callTool({name:'machine_hardware_query',arguments:{query:'cpu'}}),'machine_hardware_query');
74
+ if(hardware.status!=='ok'||hardware.query!=='cpu'||!/^[0-9a-f]{24}$/.test(hardware.receipt_id??'')||!/^[0-9a-f]{64}$/.test(hardware.observer_sha256??'')) throw new Error('hardware observation missing signed native evidence');
75
+ const hardwareVerify=await verifyObservationReceiptFile({policy,verifyKeyPath:keys.publicKeyPath,receiptId:hardware.receipt_id});
76
+ if(!hardwareVerify.ok||hardwareVerify.signature_valid!==true||hardwareVerify.request_sha256!==hardware.request_sha256) throw new Error('hardware observation receipt verification failed');
73
77
  const processes=structured(await client.callTool({name:'machine_process_list',arguments:{limit:16,include_external:false,cursor:null}}),'machine_process_list');
74
78
  if(processes.status!=='ok'||!/^[0-9a-f]{24}$/.test(processes.receipt_id??'')) throw new Error('process observation missing signed receipt');
75
79
  const started=structured(await client.callTool({name:'machine_search_start',arguments:{root_id:'core',path:'.',pattern:'needle',case_sensitive:true,
@@ -87,7 +91,7 @@ try{
87
91
  if(!searchVerify.ok||searchVerify.signature_valid!==true||searchVerify.result_head_sha256!==status.result_head_sha256) throw new Error('search receipt verification failed');
88
92
  console.log(JSON.stringify({status:'passed',protocol_revision:'2026-07-28',tool_count:names.length,
89
93
  coding_armed:false,search_results:page.results.length,process_snapshot_id:processes.snapshot_id,
90
- system_receipt_id:system.receipt_id,search_receipt_id:status.receipt_id,strict_output_schemas:true,
94
+ system_receipt_id:system.receipt_id,hardware_receipt_id:hardware.receipt_id,hardware_observer_sha256:hardware.observer_sha256,search_receipt_id:status.receipt_id,strict_output_schemas:true,
91
95
  signature_valid:true,policy_sha256:policy.policy_sha256},null,2));
92
96
  }catch(error){
93
97
  const detail=Buffer.concat(stderr).toString('utf8').trim(); if(detail) console.error(`server stderr:\n${detail}`);
@@ -226,17 +226,33 @@ export const hostFileWriteOutputSchema = statusContract({
226
226
  successStatuses: ['committed'], successRequired: ['root_id', 'path', 'previous_sha256', 'sha256', 'bytes', 'created'], errorStatuses: ['denied']
227
227
  });
228
228
 
229
- export const hostExecOutputSchema = statusContract({
230
- statuses: ['passed', 'failed', 'denied'],
231
- shape: {
232
- root_id: z.string().min(1).max(64).optional(), command_id: z.string().min(1).max(64).optional(),
233
- executable_sha256: hex64.optional(), script_sha256: hex64.optional(), exit_code: z.number().int().nullable().optional(),
234
- signal: z.string().nullable().optional(), timed_out: z.boolean().optional(), timeout_ms: z.number().int().min(100).optional(),
235
- stdout: z.string().optional(), stderr: z.string().optional()
236
- },
237
- successStatuses: ['passed', 'failed'],
238
- successRequired: ['root_id', 'command_id', 'executable_sha256', 'script_sha256', 'exit_code', 'signal', 'timed_out', 'timeout_ms', 'stdout', 'stderr'],
239
- errorStatuses: ['denied']
229
+ export const hostExecOutputSchema = z.strictObject({
230
+ status: z.enum(['passed', 'failed', 'denied']),
231
+ error: z.string().optional(),
232
+ root_id: z.string().min(1).max(64).optional(),
233
+ mode: z.enum(['script','inline','native']).optional(),
234
+ command_id: z.string().min(1).max(64).optional(),
235
+ executable_sha256: hex64.optional(),
236
+ script_sha256: hex64.optional(),
237
+ command_sha256: hex64.optional(),
238
+ argv_sha256: hex64.optional(),
239
+ stdout_sha256: hex64.optional(),
240
+ stderr_sha256: hex64.optional(),
241
+ exit_code: z.number().int().nullable().optional(),
242
+ signal: z.string().nullable().optional(),
243
+ timed_out: z.boolean().optional(),
244
+ timeout_ms: z.number().int().min(100).optional(),
245
+ stdout: z.string().optional(),
246
+ stderr: z.string().optional()
247
+ }).superRefine((value,ctx)=>{
248
+ if(value.status==='denied'){
249
+ if(!value.error) ctx.addIssue({code:'custom',path:['error'],message:'error required'});
250
+ return;
251
+ }
252
+ if(value.error!==undefined) ctx.addIssue({code:'custom',path:['error'],message:'error invalid for executed result'});
253
+ requireFields(value,ctx,['root_id','mode','command_id','executable_sha256','argv_sha256','stdout_sha256','stderr_sha256','exit_code','signal','timed_out','timeout_ms','stdout','stderr']);
254
+ if(value.mode==='script'&&value.script_sha256===undefined) ctx.addIssue({code:'custom',path:['script_sha256'],message:'script_sha256 required for script mode'});
255
+ if(value.mode==='inline'&&value.command_sha256===undefined) ctx.addIssue({code:'custom',path:['command_sha256'],message:'command_sha256 required for inline mode'});
240
256
  });
241
257
 
242
258
  export function structuredToolResult(schema, object, isError = false) {
@@ -1,4 +1,4 @@
1
- import * as z from 'zod/v4';
1
+ import * as z from 'zod/v4';
2
2
  import {
3
3
  processActionOutputSchema,
4
4
  processListOutputSchema,
@@ -6,6 +6,7 @@ import {
6
6
  processStartOutputSchema,
7
7
  processStatusOutputSchema,
8
8
  processTerminateOutputSchema,
9
+ processWriteOutputSchema,
9
10
  processStructuredResult
10
11
  } from './process-output-contracts.mjs';
11
12
 
@@ -30,14 +31,26 @@ export function registerProcessTools(server, { runtime }) {
30
31
  server.registerTool('process_session_read', {
31
32
  description: 'Read a bounded byte range from one DEADBYTE-owned process session using ordered stream offsets.',
32
33
  inputSchema: z.strictObject({
33
- session_id: hex64, offset: z.number().int().nonnegative().default(0),
34
- max_bytes: z.number().int().min(1).max(262144).default(65536)
34
+ session_id: hex64, offset: z.number().int().min(-(16 * 1024 * 1024)).default(0),
35
+ max_bytes: z.number().int().min(1).max(262144).default(65536),
36
+ wait_ms: z.number().int().min(0).max(30000).default(0),
37
+ wait_for: z.string().min(1).max(256).nullable().default(null)
35
38
  }), outputSchema: processReadOutputSchema
36
- }, async ({ session_id, offset, max_bytes }) => {
37
- try { return ok(processReadOutputSchema, { status: 'ok', ...(await runtime.read({ sessionId: session_id, offset, maxBytes: max_bytes })) }); }
39
+ }, async ({ session_id, offset, max_bytes, wait_ms, wait_for }) => {
40
+ try { return ok(processReadOutputSchema, { status: 'ok', ...(await runtime.read({ sessionId: session_id, offset, maxBytes: max_bytes, waitMs: wait_ms, waitFor: wait_for })) }); }
38
41
  catch (error) { return fail(processReadOutputSchema, 'denied', error); }
39
42
  });
40
43
 
44
+ server.registerTool('process_session_write', {
45
+ description: 'Write one bounded UTF-8 input payload to a running DEADBYTE-owned pipe session only when its local profile explicitly enables raw input. Input text is never stored in the signed journal; only byte count and SHA-256 identity are recorded.',
46
+ inputSchema: z.strictObject({
47
+ session_id: hex64, input: z.string().min(1).max(1024 * 1024), append_newline: z.boolean().default(true)
48
+ }), outputSchema: processWriteOutputSchema
49
+ }, async ({ session_id, input, append_newline }) => {
50
+ try { return ok(processWriteOutputSchema, { status: 'ok', ...(await runtime.write({ sessionId: session_id, input, appendNewline: append_newline })) }); }
51
+ catch (error) { return fail(processWriteOutputSchema, 'denied', error); }
52
+ });
53
+
41
54
  server.registerTool('process_session_status', {
42
55
  description: 'Read the verified state of one DEADBYTE-owned process session by session identity.',
43
56
  inputSchema: z.strictObject({ session_id: hex64 }), outputSchema: processStatusOutputSchema