deadbyte-mcp 0.15.42 → 0.15.44

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 (132) hide show
  1. package/MANIFEST.SHA256 +131 -68
  2. package/PROVENANCE.json +37 -13
  3. package/README.txt +18 -18
  4. package/RELEASE.json +40 -14
  5. package/SBOM.cdx.json +14 -2
  6. package/TEST-MATRIX.json +52 -10
  7. package/bin/appcontainer-stage.obj +0 -0
  8. package/bin/bootstrap-advapi32.obj +0 -0
  9. package/bin/bootstrap-exitcode.obj +0 -0
  10. package/bin/bootstrap-kernel32.obj +0 -0
  11. package/bin/child-control-probe.obj +0 -0
  12. package/bin/child-control-stage.obj +0 -0
  13. package/bin/contained-reverse-worker.obj +0 -0
  14. package/bin/contained-transform-worker.obj +0 -0
  15. package/bin/containment-probe.obj +0 -0
  16. package/bin/deadbyte-contain.obj +0 -0
  17. package/bin/deadbyte-desktop-input-r40.obj +0 -0
  18. package/bin/deadbyte-desktop-observer-r40.obj +0 -0
  19. package/bin/deadbyte-desktop-uia-r40.obj +0 -0
  20. package/bin/deadbyte-exec.obj +0 -0
  21. package/bin/deadbyte-hardware-observer-r45.obj +0 -0
  22. package/bin/deadbyte-native-inspect-r67.exe +0 -0
  23. package/bin/deadbyte-native-inspect-r67.obj +0 -0
  24. package/bin/deadbyte-process-host.obj +0 -0
  25. package/bin/deadbyte-tunnel-host.obj +0 -0
  26. package/bridge/package.json +1 -1
  27. package/controller/deadbyte-desktop-policy.json +5 -5
  28. package/controller/deadbyte-process-policy.json +4 -4
  29. package/docs/R67-ALPHA-REFERENCE-DIFFERENTIAL.md +42 -0
  30. package/docs/R67-ALPHA-STATIC-COMPARISON.json +205 -0
  31. package/docs/R67-CANDIDATE-PARENT.json +10 -0
  32. package/docs/R67-EVIDENCE-RECONCILE-CLOSEOUT-20261011.md +64 -0
  33. package/docs/R67-FULL-REGRESSION-UNKNOWN.json +27 -0
  34. package/docs/R67-NATIVE-INSPECT-BINARY.json +11 -0
  35. package/docs/R67-NPM-EXDEV-PRESERVATION-20261011.md +39 -0
  36. package/docs/R67-OBSERVATION-TRUTH.md +82 -0
  37. package/docs/R67-REFERENCE-GAP-MATRIX.json +57 -0
  38. package/docs/R67-REGRESSION-CLOSURE-20261011.md +24 -0
  39. package/native/deadbyte-native-inspect-r67.cpp +252 -0
  40. package/package.json +7 -3
  41. package/proof/CONTAINMENT-BUILD.txt +6 -6
  42. package/scripts/build-native-inspector-r67.ps1 +28 -0
  43. package/scripts/gate-windows-r66-prepublish.ps1 +65 -0
  44. package/scripts/gate-windows-r67.ps1 +56 -0
  45. package/scripts/npm-preserve-artifact-r67.mjs +99 -0
  46. package/scripts/npm-release-r48.mjs +10 -2
  47. package/scripts/npm-release-readiness-r67.mjs +26 -0
  48. package/scripts/r67-acquire-alpha-reference.mjs +95 -0
  49. package/scripts/r67-audit-nonpass-source.mjs +39 -0
  50. package/scripts/r67-compare-desktop-alpha.mjs +48 -0
  51. package/scripts/r67-diagnose-failed-shard.mjs +23 -0
  52. package/scripts/r67-durable-batch.mjs +42 -0
  53. package/scripts/r67-focused-gate.mjs +58 -0
  54. package/scripts/r67-full-regression.mjs +28 -0
  55. package/scripts/r67-independent-observation-verify.mjs +36 -0
  56. package/scripts/r67-independent-runtime-observation-verify.mjs +63 -0
  57. package/scripts/r67-nonpass-adjudicate.mjs +20 -0
  58. package/scripts/r67-publish-boundary-audit.mjs +32 -0
  59. package/scripts/r67-regression-reconcile.mjs +10 -0
  60. package/scripts/r67-regression-shard-run.mjs +70 -0
  61. package/scripts/r67-run-shard-batch.mjs +18 -0
  62. package/scripts/r67-stage-candidate.mjs +30 -0
  63. package/scripts/r67-verify-parent-ping.mjs +22 -0
  64. package/scripts/release-manifest.mjs +1 -0
  65. package/src/activation-continuity-engine-r65.mjs +2 -1
  66. package/src/autonomous-runtime.mjs +1 -1
  67. package/src/deadbyte-cli.mjs +33 -1
  68. package/src/doctor-runtime-r58.mjs +3 -1
  69. package/src/machine-fs-smoke-client.mjs +13 -5
  70. package/src/machine-identity.mjs +59 -2
  71. package/src/machine-mcp-tools.mjs +8 -0
  72. package/src/mcp-server.mjs +11 -2
  73. package/src/native-inspector-r67.mjs +66 -0
  74. package/src/r67-diagnostic-export.mjs +118 -0
  75. package/src/r67-diagnostics-verify.mjs +82 -0
  76. package/src/r67-evidence-reconcile.mjs +128 -0
  77. package/src/r67-native-observation-authority.mjs +45 -0
  78. package/src/r67-observation-receipt.mjs +43 -0
  79. package/src/r67-regression-shards.mjs +176 -0
  80. package/src/remote-live-log.mjs +14 -10
  81. package/src/tool-audit.mjs +1 -1
  82. package/src/version.mjs +1 -1
  83. package/test/agent-runtime.test.mjs +1 -1
  84. package/test/autonomous-loop.test.mjs +11 -6
  85. package/test/autonomous-runtime.test.mjs +1 -1
  86. package/test/coding-plane.test.mjs +1 -1
  87. package/test/full-max-catalog-r46.test.mjs +1 -1
  88. package/test/output-schema.test.mjs +1 -1
  89. package/test/r51-live-log-polling.test.mjs +12 -0
  90. package/test/r53-release-contract.test.mjs +1 -1
  91. package/test/r54-release-contract.test.mjs +1 -1
  92. package/test/r55-release-contract.test.mjs +1 -1
  93. package/test/r56-release-contract.test.mjs +1 -1
  94. package/test/r57-release-contract.test.mjs +1 -1
  95. package/test/r58-bundled-bridge.test.mjs +1 -1
  96. package/test/r58-doctor-runtime.test.mjs +18 -0
  97. package/test/r58-release-contract.test.mjs +2 -2
  98. package/test/r58-windows-gate.test.mjs +1 -1
  99. package/test/r59-tool-surface-compat.test.mjs +1 -1
  100. package/test/r60-release-contract.test.mjs +3 -3
  101. package/test/r61-release-contract.test.mjs +1 -1
  102. package/test/r62-release-contract.test.mjs +1 -1
  103. package/test/r63-release-contract.test.mjs +1 -1
  104. package/test/r64-release-contract.test.mjs +1 -1
  105. package/test/r65-epoch-native-activation-integration.test.mjs +4 -4
  106. package/test/r65-release-contract.test.mjs +2 -1
  107. package/test/r66-release-contract.test.mjs +57 -0
  108. package/test/r67-cli-integration.test.mjs +41 -0
  109. package/test/r67-diagnostics-cli.test.mjs +60 -0
  110. package/test/r67-diagnostics-verify.test.mjs +49 -0
  111. package/test/r67-diagnostics-zip-oracle.test.mjs +38 -0
  112. package/test/r67-diagnostics.test.mjs +60 -0
  113. package/test/r67-evidence-reconcile.test.mjs +186 -0
  114. package/test/r67-isolated-slot-rollback-restore.test.mjs +62 -0
  115. package/test/r67-live-log-secret-canary.test.mjs +44 -0
  116. package/test/r67-machine-fs-smoke-epoch.test.mjs +22 -0
  117. package/test/r67-native-inspector.test.mjs +131 -0
  118. package/test/r67-native-observation-authority.test.mjs +49 -0
  119. package/test/r67-npm-cross-volume.test.mjs +74 -0
  120. package/test/r67-npm-publish-gate.test.mjs +38 -0
  121. package/test/r67-regression-shards.test.mjs +184 -0
  122. package/test/r67-release-contract.test.mjs +44 -0
  123. package/test/r67-runtime-observation-binding.test.mjs +86 -0
  124. package/test/r67-server-observation-wiring.test.mjs +12 -0
  125. package/test/r67-signed-receipt.test.mjs +66 -0
  126. package/test/r67-skip-rationale.test.mjs +31 -0
  127. package/test/r67-tool-annotations.test.mjs +21 -0
  128. package/test/r67-windows-gate-argv.test.mjs +13 -0
  129. package/test/release-version.test.mjs +1 -1
  130. package/test/remote-live-log-r36.test.mjs +10 -0
  131. package/test/remote-live-log-r37.test.mjs +29 -0
  132. package/test/semantic-multilang.test.mjs +1 -1
@@ -0,0 +1,18 @@
1
+ import {spawnSync} from 'node:child_process';
2
+ import {readFile} from 'node:fs/promises';
3
+ import {fileURLToPath} from 'node:url';
4
+ import path from 'node:path';
5
+ const root=path.dirname(path.dirname(fileURLToPath(import.meta.url)));
6
+ const op=process.argv[2],start=Number(process.argv[3]),end=Number(process.argv[4]);
7
+ if(!/^[a-z0-9][a-z0-9._-]{5,63}$/.test(op??'')||!Number.isInteger(start)||!Number.isInteger(end)||start<0||end>32||end<=start||end-start>8)throw Error('usage: operation id, start, end (max eight)');
8
+ const runner=path.join(root,'scripts','r67-regression-shard-run.mjs');
9
+ const results=[];
10
+ for(let i=start;i<end;i++){
11
+ const found=spawnSync(process.execPath,[runner,'run',op,String(i)],{cwd:root,encoding:'utf8',shell:false,windowsHide:true,timeout:220000,maxBuffer:256*1024});
12
+ const receiptPath=path.resolve(root,'..','..','testing','0.15.44-r67','regression-shards',op+'-shard-'+String(i).padStart(2,'0'),'result.json');
13
+ let body=null;try{body=JSON.parse(await readFile(receiptPath,'utf8'));}catch{}
14
+ results.push({shard_index:i,transport_exit:found.status,child_error:found.error?.code??null,recorded_status:body?.status??'MISSING',test_count:body?.test_count??null,passed:body?.passed??null,skipped:body?.skipped??null,nonpass_classified:body?.nonpass_fully_classified??null,source_tree_sha256:body?.source_tree_sha256??null,receipt_sha256:body?.receipt_sha256??null});
15
+ if(!body){break;}
16
+ }
17
+ console.log(JSON.stringify({schema:'deadbyte.r67-batch-progress.v1',operation_id:op,results},null,2));
18
+ if(results.length!==end-start||results.some(r=>r.recorded_status!=='PASS'))process.exitCode=2;
@@ -0,0 +1,30 @@
1
+ import {readFile,writeFile,stat,lstat,mkdir,copyFile} from 'node:fs/promises';
2
+ import {createHash} from 'node:crypto';
3
+ import path from 'node:path';
4
+ import {fileURLToPath} from 'node:url';
5
+ const candidate=path.dirname(path.dirname(fileURLToPath(import.meta.url)));
6
+ const parent=path.join(path.dirname(candidate),'deadbyte-mcp-0.15.43-r66');
7
+ const EXPECT='4c4a64a4c8e9f5a6e9eecce85fd0007ebcac99121b29c07f1a1fc81d1643cd33';
8
+ const sha=b=>createHash('sha256').update(b).digest('hex');
9
+ const manifest=await readFile(path.join(parent,'MANIFEST.SHA256'));
10
+ if(sha(manifest)!==EXPECT)throw new Error('R67 parent manifest SHA mismatch');
11
+ const rows=manifest.toString('utf8').trimEnd().split(/\r?\n/).map(line=>{const m=/^([a-f0-9]{64}) (.+)$/.exec(line);if(!m)throw new Error('bad manifest row');return {sha:m[1],rel:m[2]};});
12
+ const seen=new Set();
13
+ let copied=0,bytes=0;
14
+ for(const {sha:want,rel} of rows){
15
+ if(rel.startsWith('/')||rel.includes('\\')||rel.split('/').some(x=>!x||x==='.'||x==='..')||seen.has(rel))throw new Error('unsafe or duplicate parent path: '+rel);
16
+ seen.add(rel);
17
+ const src=path.join(parent,...rel.split('/'));const info=await lstat(src);
18
+ if(!info.isFile()||info.isSymbolicLink())throw new Error('non-file predecessor: '+rel);
19
+ const data=await readFile(src);if(sha(data)!==want)throw new Error('source drift: '+rel);
20
+ const dst=path.join(candidate,...rel.split('/'));
21
+ await mkdir(path.dirname(dst),{recursive:true});
22
+ try{const existing=await readFile(dst);if(sha(existing)!==want)throw new Error('destination differs: '+rel);}catch(e){if(e.code!=='ENOENT')throw e;await copyFile(src,dst);}
23
+ if(sha(await readFile(dst))!==want)throw new Error('copy differs: '+rel);
24
+ copied++;bytes+=data.length;
25
+ }
26
+ await writeFile(path.join(candidate,'MANIFEST.SHA256'),manifest,{flag:'wx'});
27
+ const proof={schema:'deadbyte.r67-candidate-parent.v1',parent_release_id:'v0.15.43-4c4a64a4c8e9f5a6',parent_manifest_sha256:EXPECT,source_directory:parent,candidate_directory:candidate,copied_files:copied,copied_bytes:bytes,mutation:false};
28
+ await mkdir(path.join(candidate,'docs'),{recursive:true});
29
+ await writeFile(path.join(candidate,'docs','R67-CANDIDATE-PARENT.json'),JSON.stringify(proof,null,2)+'\n',{flag:'wx'});
30
+ console.log(JSON.stringify({status:'STAGED',copied_files:copied,copied_bytes:bytes,parent_manifest_sha256:EXPECT,candidate},null,2));
@@ -0,0 +1,22 @@
1
+ import {readFile} from 'node:fs/promises';
2
+ import {createHash,createPublicKey,verify as edVerify} from 'node:crypto';
3
+ import path from 'node:path';
4
+ import {fileURLToPath} from 'node:url';
5
+ const root=path.resolve(fileURLToPath(new URL('../../..',import.meta.url)));
6
+ const receiptFile=path.join(root,'testing','0.15.44-r67','R67-PRODUCTION-BASELINE.json');
7
+ const receipt=JSON.parse(await readFile(receiptFile,'utf8'));
8
+ const ping=receipt.signed_ping;
9
+ const spki=Buffer.from(receipt.machine.public_key_spki_base64,'base64');
10
+ const keyId=createHash('sha256').update(spki).digest('hex');
11
+ const {attestation,...body}=ping;
12
+ const sorted=v=>Array.isArray(v)?v.map(sorted):v!==null&&typeof v==='object'?Object.fromEntries(Object.keys(v).sort().map(k=>[k,sorted(v[k])])):v;
13
+ const message=Buffer.concat([Buffer.from('DEADBYTE-MACHINE-PING-V1\0','utf8'),Buffer.from(JSON.stringify(sorted(body)),'utf8')]);
14
+ const digest=createHash('sha256').update(message).digest('hex');
15
+ const key=createPublicKey({key:spki,format:'der',type:'spki'});
16
+ const signature=Buffer.from(attestation.signature_base64,'base64');
17
+ const valid=key.asymmetricKeyType==='ed25519'&&signature.length===64&&attestation.key_id===keyId&&ping.machine_id===keyId&&receipt.machine.machine_id===keyId&&edVerify(null,message,key,signature);
18
+ const prior=receipt.independent_verification?.signed_message_sha256??null;
19
+ const ok=valid&&digest===prior;
20
+ const result={schema:'deadbyte.r67-independent-baseline-check.v1',status:ok?'PASS':'FAIL',ed25519_valid:valid,key_identity_matches:keyId===ping.machine_id,message_sha256:digest,claimed_message_sha256:prior,release_id:ping.release_id};
21
+ console.log(JSON.stringify(result,null,2));
22
+ if(!ok)process.exitCode=1;
@@ -59,6 +59,7 @@ function npmPackFiles() {
59
59
  const raw = item?.path;
60
60
  if (typeof raw !== 'string' || raw.length < 1) throw new Error('npm pack file path invalid');
61
61
  const rel = raw.replaceAll('\\','/');
62
+ if (/\.tgz$/i.test(rel)) throw new Error(`npm archive self-inclusion forbidden: ${rel}; exclude existing tarballs from the npm package surface`);
62
63
  if (rel === manifestName || excludedPackagePaths.has(rel) || excludedPackagePrefixes.some(prefix => rel.startsWith(prefix))) continue;
63
64
  if (path.posix.isAbsolute(rel) || rel.split('/').includes('..') || rel.includes('\0')) {
64
65
  throw new Error(`unsafe package path '${rel}'`);
@@ -2,6 +2,7 @@ import {createHash,randomBytes} from 'node:crypto';
2
2
  import {open,readFile,rm} from 'node:fs/promises';
3
3
  import path from 'node:path';
4
4
  import {canonicalJson} from './canonical-json.mjs';
5
+ import {decodeUtf8Strict} from './text-policy.mjs';
5
6
  import {
6
7
  captureStandingGrant,compareSecurityEnvelopes,createAuthorityEpochStore,deriveSecurityEnvelope
7
8
  } from './authority-continuity-r65.mjs';
@@ -17,7 +18,7 @@ const sha=bytes=>createHash('sha256').update(bytes).digest('hex');
17
18
  const plain=value=>value&&typeof value==='object'&&!Array.isArray(value);
18
19
 
19
20
  function parseJson(bytes,label){
20
- try{return JSON.parse(bytes.toString('utf8'));}
21
+ try{return JSON.parse(decodeUtf8Strict(bytes));}
21
22
  catch{throw new Error(`${label} JSON invalid`);}
22
23
  }
23
24
  function inside(root,target){
@@ -308,7 +308,7 @@ async function executeProfile({ ctx, events, goalId, actionKey, profileId, epoch
308
308
  const r34Paused=await pauseR34Ambiguous(checked.reason ?? error);
309
309
  if (r34Paused) return r34Paused;
310
310
  await appendEvent(ctx,events,signingKeyPath,'goal_paused',{ reason:'profile_execution_without_receipt', profile_id:profileId,
311
- action_key:actionKey, detail:truncated(error) });
311
+ action_key:actionKey, receipt_id:receiptId, request_sha256:requestSha256, detail:truncated(error,512) });
312
312
  return { status:'paused', observation:null, existing:false };
313
313
  }
314
314
  result = null;
@@ -11,6 +11,9 @@ import { defaultPublicKeyPath } from './trust.mjs';
11
11
  import { verifyAuditChain } from './audit-verify.mjs';
12
12
  import { openRemoteResultJournal, parseRemoteResultCursor } from './remote-result-journal.mjs';
13
13
  import { DEADBYTE_VERSION } from './version.mjs';
14
+ import { runNativeInspect } from './native-inspector-r67.mjs';
15
+ import { exportOfflineDiagnostics } from './r67-diagnostic-export.mjs';
16
+ import { verifyOfflineDiagnosticsZip } from './r67-diagnostics-verify.mjs';
14
17
  import { parseActivePointer } from './release-slot.mjs';
15
18
  import { printDoctor } from './doctor-r45.mjs';
16
19
  import { createLiveTimelineFilter, createLiveTimelineTracker, createLiveTimelineWriter, formatCompactAuditEvent, formatRemoteLifecycle, formatRemoteToolCall, formatRuntimeDiagnosticLine, selectLiveLogNames } from './remote-live-log.mjs';
@@ -60,7 +63,14 @@ function printHelp(){
60
63
  ` npx -y ${exact} supervisor status`,
61
64
  ` npx -y ${exact} supervisor recover`,
62
65
  ` npx -y ${exact} audit verify`,
63
- ` npx -y ${exact} audit status`,''
66
+ ` npx -y ${exact} audit status`,
67
+ ` npx -y ${exact} diagnostics export --output <local.zip> # offline, never uploads`,
68
+ ` npx -y ${exact} diagnostics verify --input <local.zip> # local structural/CRC verify`,
69
+ ` npx -y ${exact} inspect process [limit] # local native process snapshot`,
70
+ ` npx -y ${exact} inspect identity PID # native process creation token`,
71
+ ` npx -y ${exact} inspect memory self [limit] # local VirtualQueryEx metadata only`,
72
+ ` npx -y ${exact} inspect memory PID <creation_filetime> [limit] # PID-reuse guarded`,
73
+ ` npx -y ${exact} inspect pe <file> # local native PE32/PE32+ inspection`,''
64
74
  ].join('\n'));
65
75
  }
66
76
  async function remoteContext(){
@@ -681,6 +691,28 @@ async function main(argv){
681
691
  if(command==='remote'){await runRemote(rest);return;}
682
692
  if(command==='supervisor'){await runSupervisor(rest);return;}
683
693
  if(command==='audit'){await runAudit(rest);return;}
694
+ if(command==='diagnostics'){
695
+ if(rest.length!==3)throw new Error('diagnostics export --output or verify --input; no network upload supported');
696
+ if(rest[0]==='export'&&rest[1]==='--output'){
697
+ const report=await exportOfflineDiagnostics({stateRoot:stateRoot(),outputPath:rest[2]});
698
+ process.stdout.write(`${JSON.stringify(report)}\n`);
699
+ return;
700
+ }
701
+ if(rest[0]==='verify'&&rest[1]==='--input'){
702
+ const file=path.resolve(rest[2]);
703
+ await requireRegular(file,'diagnostic ZIP');
704
+ const report=verifyOfflineDiagnosticsZip(await readFile(file));
705
+ process.stdout.write(`${JSON.stringify(report)}\n`);
706
+ return;
707
+ }
708
+ throw new Error('unsupported diagnostics mode; network upload is forbidden');
709
+ }
710
+ if(command==='inspect'){
711
+ const result=await runNativeInspect(rest);
712
+ process.stdout.write(`${JSON.stringify(result)}\n`);
713
+ if(result.status!=='ok')process.exitCode=2;
714
+ return;
715
+ }
684
716
  throw new Error(`unknown command '${command}'`);
685
717
  }
686
718
 
@@ -28,8 +28,10 @@ export function validateDoctorControllerObservation(value,expectedCoreRoot){
28
28
  if(typeof value.supervisor.configured!=='boolean'||typeof value.supervisor.running!=='boolean'||!reason(value.supervisor.reason)||(!value.supervisor.configured&&value.supervisor.running))throw new Error('supervisor observation invalid');
29
29
  object(value.authority,['process','coding','autonomous','desktop','host']);
30
30
  for(const item of Object.values(value.authority)){
31
- object(item,['configured','armed','reason']);
31
+ const hasEpoch=Object.hasOwn(item,'epoch_seq')||Object.hasOwn(item,'epoch_sha256');
32
+ object(item,hasEpoch?['configured','armed','reason','epoch_seq','epoch_sha256']:['configured','armed','reason']);
32
33
  if(typeof item.configured!=='boolean'||typeof item.armed!=='boolean'||!reason(item.reason)||(!item.configured&&item.armed)||(item.armed&&item.reason!=='standing-grant'))throw new Error('authority observation invalid');
34
+ if(hasEpoch&&(!item.armed||!Number.isSafeInteger(item.epoch_seq)||item.epoch_seq<1||typeof item.epoch_sha256!=='string'||!/^[0-9a-f]{64}$/.test(item.epoch_sha256)))throw new Error('authority epoch observation invalid');
33
35
  }
34
36
  return value;
35
37
  }
@@ -6,6 +6,7 @@ import { fileURLToPath } from 'node:url';
6
6
  import { Client } from '@modelcontextprotocol/client';
7
7
  import { StdioClientTransport, getDefaultEnvironment } from '@modelcontextprotocol/client/stdio';
8
8
  import { CODING_GRANT_SCHEMA, loadCodingPolicy } from './coding-plane.mjs';
9
+ import { createAuthorityEpochStore } from './authority-continuity-r65.mjs';
9
10
  import { verifyMachineFsReceiptFile } from './machine-fs.mjs';
10
11
  import { generateTrustKeypair } from './trust.mjs';
11
12
  import { assertExactCatalog, expectedFullCatalogForEnv } from './runtime-catalog-expectation.mjs';
@@ -23,14 +24,16 @@ const structured = (result, label) => {
23
24
  const scratch = await mkdtemp(path.join(os.tmpdir(), 'deadbyte-machine-fs-smoke-'));
24
25
  const coreRoot = path.join(scratch, 'core');
25
26
  const trustRoot = path.join(scratch, 'trust');
27
+ const runtimeRoot = path.join(scratch, 'runtime');
26
28
  await mkdir(coreRoot, { recursive: true });
27
29
  await mkdir(trustRoot, { recursive: true });
30
+ await mkdir(runtimeRoot, { recursive: true });
28
31
  const keys = await generateTrustKeypair({
29
- privateKeyPath: path.join(trustRoot, 'private.pem'),
30
- publicKeyPath: path.join(trustRoot, 'public.pem')
32
+ privateKeyPath: path.join(trustRoot, 'ed25519-private.pem'),
33
+ publicKeyPath: path.join(trustRoot, 'ed25519-public.pem')
31
34
  });
32
35
  const policyPath = path.join(scratch, 'coding-policy.json');
33
- const grantPath = path.join(scratch, 'coding-grant.json');
36
+ const grantPath = path.join(runtimeRoot, 'coding-grant.json');
34
37
  const evidenceDir = path.join(scratch, 'evidence');
35
38
  const rawPolicy = {
36
39
  schema: 'deadbyte.coding-policy.v1', enabled: true,
@@ -41,11 +44,16 @@ const rawPolicy = {
41
44
  const policyBytes = Buffer.from(`${JSON.stringify(rawPolicy, null, 2)}\n`, 'utf8');
42
45
  await writeFile(policyPath, policyBytes, { flag: 'wx' });
43
46
  const policy = await loadCodingPolicy(policyPath);
44
- await writeFile(grantPath, `${JSON.stringify({
47
+ const grantBytes = Buffer.from(`${JSON.stringify({
45
48
  schema: CODING_GRANT_SCHEMA, policy_sha256: policy.policy_sha256,
46
49
  instance_nonce: randomBytes(32).toString('hex'), root_ids: ['core'],
47
50
  granted_at_utc: new Date().toISOString()
48
- }, null, 2)}\n`, { flag: 'wx' });
51
+ }, null, 2)}\n`, 'utf8');
52
+ await writeFile(grantPath, grantBytes, { flag: 'wx' });
53
+ const grant = JSON.parse(grantBytes.toString('utf8'));
54
+ await createAuthorityEpochStore({ runtimeDir:runtimeRoot, policyBytes }).append({
55
+ plane:'coding', action:'grant', grantBytes, grantInstanceNonce:grant.instance_nonce, updateId:null
56
+ });
49
57
 
50
58
  const client = new Client({ name: 'deadbyte-machine-fs-smoke', version: '0.8.0' },
51
59
  { versionNegotiation: { mode: { pin: '2026-07-28' } } });
@@ -11,11 +11,13 @@ import { lstat, mkdir, open, readFile, rename, rm, writeFile } from 'node:fs/pro
11
11
  import os from 'node:os';
12
12
  import path from 'node:path';
13
13
  import { canonicalJson } from './canonical-json.mjs';
14
+ import {nativeInspectArgs,runNativeInspect} from './native-inspector-r67.mjs';
14
15
  import { parseActivePointer } from './release-slot.mjs';
15
16
 
16
17
  export const MACHINE_IDENTITY_SCHEMA = 'deadbyte.machine-identity.v1';
17
18
  export const MACHINE_PING_SCHEMA = 'deadbyte.machine-ping.v1';
18
19
  const MACHINE_PING_DOMAIN = Buffer.from('DEADBYTE-MACHINE-PING-V1\0', 'utf8');
20
+ export const R67_MACHINE_OBSERVATION_DOMAIN=Buffer.from('DEADBYTE-R67-MACHINE-OBSERVATION-V1\0','utf8');
19
21
  const HEX64 = /^[0-9a-f]{64}$/;
20
22
 
21
23
  const sha256 = bytes => createHash('sha256').update(bytes).digest('hex');
@@ -182,7 +184,8 @@ function statusBody(identity, pointer, onlineEpoch) {
182
184
 
183
185
  export async function createMachineRuntime({
184
186
  stateRoot = path.join(os.homedir(), '.deadbyte-mcp'),
185
- activePointerPath = path.join(stateRoot, 'active-release.json')
187
+ activePointerPath = path.join(stateRoot, 'active-release.json'),
188
+ assertObservationAuthority = null
186
189
  } = {}) {
187
190
  await initializeMachineIdentity({ stateRoot });
188
191
  let identity = await loadIdentity(stateRoot);
@@ -238,7 +241,61 @@ export async function createMachineRuntime({
238
241
  identity_generation: identity.state.identity_generation, online_epoch: onlineEpoch };
239
242
  }
240
243
 
241
- return Object.freeze({ list, status, ping, revoke });
244
+
245
+ // A non-public server-owned signing path: observer + machine identity stay in one process.
246
+ // The MCP bridge must never expose this as a caller-supplied signing oracle.
247
+ async function observeNative({args}){
248
+ if(typeof assertObservationAuthority!=='function')throw new Error('R67 observation authority callback required');
249
+ if(!Array.isArray(args))throw new Error('R67 observation args required');
250
+ const nativeArgs=nativeInspectArgs(args);
251
+ const operation=nativeArgs[0];
252
+ const before=await status();
253
+ const checkGrant=grant=>{
254
+ if(!grant||!/^[-a-zA-Z0-9._:]{1,128}$/.test(grant.grant_id??'')||
255
+ !HEX64.test(grant.policy_sha256??'')||grant.scope!=='native-observe-readonly'||
256
+ !Number.isSafeInteger(grant.authority_generation)||grant.authority_generation<1)
257
+ throw new Error('R67 authority grant invalid');
258
+ return {grant_id:grant.grant_id,policy_sha256:grant.policy_sha256,
259
+ scope:grant.scope,authority_generation:grant.authority_generation};
260
+ };
261
+ const first=checkGrant(await assertObservationAuthority({phase:'pre',operation,args:nativeArgs,machine:before}));
262
+ const result=await runNativeInspect(args);
263
+ const after=await status();
264
+ const second=checkGrant(await assertObservationAuthority({phase:'post',operation,args:nativeArgs,machine:after}));
265
+ if(canonicalJson(first)!==canonicalJson(second))throw new Error('R67 authority lease changed during observation');
266
+ for(const field of ['machine_id','identity_generation','online_epoch','release_id','release_manifest_sha256','generation_manifest_sha256'])
267
+ if(before[field]!==after[field])throw new Error('R67 machine or release generation changed during observation');
268
+ const pinned=JSON.parse(await readFile(new URL('../docs/R67-NATIVE-INSPECT-BINARY.json',import.meta.url),'utf8'));
269
+ if(pinned.schema!=='deadbyte.r67-native-inspector-binary.v1'||!HEX64.test(pinned.sha256??''))
270
+ throw new Error('R67 observer executable pin invalid');
271
+ // Trusted signer is loaded from the machine identity store by this runtime, never provided by a client.
272
+ identity=await loadIdentity(stateRoot);
273
+ if(identity.machine_id!==before.machine_id||identity.state.identity_generation!==before.identity_generation)
274
+ throw new Error('R67 identity revoked before signing');
275
+ const receiptBody={
276
+ schema:'deadbyte.native-observation-runtime-r67.v1',
277
+ observed_at_utc:new Date().toISOString(),
278
+ machine_id:before.machine_id,
279
+ identity_generation:before.identity_generation,
280
+ online_epoch:before.online_epoch,
281
+ release_id:before.release_id,
282
+ release_manifest_sha256:before.release_manifest_sha256,
283
+ generation_manifest_sha256:before.generation_manifest_sha256,
284
+ authority:first,
285
+ operation,
286
+ request_sha256:sha256(Buffer.from(JSON.stringify(nativeArgs),'utf8')),
287
+ executable_sha256:pinned.sha256,
288
+ result_status:result.status,
289
+ result_sha256:sha256(Buffer.from(canonicalJson(result),'utf8'))
290
+ };
291
+ const signature=cryptoSign(null,Buffer.concat([R67_MACHINE_OBSERVATION_DOMAIN,
292
+ Buffer.from(canonicalJson(receiptBody),'utf8')]),identity.privateKey);
293
+ return {result,receipt:{...receiptBody,attestation:{
294
+ algorithm:'ed25519',key_id:identity.machine_id,signature_base64:signature.toString('base64')
295
+ }}};
296
+ }
297
+
298
+ return Object.freeze({ list, status, ping, revoke, observeNative });
242
299
  }
243
300
 
244
301
  export async function verifyMachinePingPublic({ ping, publicKeySpkiBase64 }) {
@@ -7,6 +7,10 @@ import {
7
7
  machineStructuredResult
8
8
  } from './machine-output-contracts.mjs';
9
9
 
10
+ const READ_HINTS = Object.freeze({ readOnlyHint:true, destructiveHint:false, idempotentHint:true, openWorldHint:false });
11
+ const SIGNED_PING_HINTS = Object.freeze({ readOnlyHint:true, destructiveHint:false, idempotentHint:false, openWorldHint:false });
12
+ const REVOKE_HINTS = Object.freeze({ readOnlyHint:false, destructiveHint:true, idempotentHint:false, openWorldHint:false });
13
+
10
14
  function ok(schema, value) { return machineStructuredResult(schema, value, false); }
11
15
  function fail(schema, status, error) { return machineStructuredResult(schema, { status, error: String(error) }, true); }
12
16
 
@@ -16,21 +20,25 @@ export function registerMachineTools(server, { runtime, assertRevokeAuthority })
16
20
 
17
21
  server.registerTool('machine_list', {
18
22
  description: 'List DEADBYTE machine identities visible to this runtime. Identity is public-key based; hostname is informational.',
23
+ annotations: READ_HINTS,
19
24
  inputSchema: z.strictObject({}), outputSchema: machineListOutputSchema
20
25
  }, async () => { try { return ok(machineListOutputSchema, await runtime.list()); } catch (error) { return fail(machineListOutputSchema, 'failed', error); } });
21
26
 
22
27
  server.registerTool('machine_status', {
23
28
  description: 'Read this machine identity, online epoch, active release/generation binding, and capability set.',
29
+ annotations: READ_HINTS,
24
30
  inputSchema: z.strictObject({}), outputSchema: machineStatusOutputSchema
25
31
  }, async () => { try { return ok(machineStatusOutputSchema, await runtime.status()); } catch (error) { return fail(machineStatusOutputSchema, 'failed', error); } });
26
32
 
27
33
  server.registerTool('machine_ping', {
28
34
  description: 'Return an Ed25519-signed nonce echo bound to machine identity, online epoch, release, and generation.',
35
+ annotations: SIGNED_PING_HINTS,
29
36
  inputSchema: z.strictObject({ nonce: z.string().min(1).max(256) }), outputSchema: machinePingOutputSchema
30
37
  }, async ({ nonce }) => { try { return ok(machinePingOutputSchema, await runtime.ping({ nonce })); } catch (error) { return fail(machinePingOutputSchema, 'failed', error); } });
31
38
 
32
39
  server.registerTool('machine_revoke', {
33
40
  description: 'Invalidate the current machine online epoch/generation. Requires independently armed local host authority and never expands authority.',
41
+ annotations: REVOKE_HINTS,
34
42
  inputSchema: z.strictObject({ reason: z.string().min(1).max(256) }), outputSchema: machineRevokeOutputSchema
35
43
  }, async ({ reason }) => {
36
44
  try {
@@ -20,6 +20,7 @@ import { recoverInterruptedSlotUpdates } from './runtime-update.mjs';
20
20
  import { decodeUtf8Strict } from './text-policy.mjs';
21
21
  import { assertHostArmed, hostExec, hostExecInline, hostExecNative, hostFileRead, hostFileWrite, loadHostPolicy } from './host-plane.mjs';
22
22
  import { createMachineRuntime } from './machine-identity.mjs';
23
+ import { createR67NativeObservationAuthority } from './r67-native-observation-authority.mjs';
23
24
  import { registerMachineTools } from './machine-mcp-tools.mjs';
24
25
  import { createAgentRuntime } from './agent-runtime.mjs';
25
26
  import { registerAgentTools } from './agent-mcp-tools.mjs';
@@ -107,10 +108,19 @@ if (!privilegedPolicyExplicit) {
107
108
  }
108
109
  planeRegistry.define('host',{configured:Boolean(privilegedPolicyPath),required:false});
109
110
  const hostPolicy=await planeRegistry.initialize('host',async()=>loadHostPolicy(privilegedPolicyPath));
111
+ // The machine runtime is initialized before Coding. Authority is resolved lazily
112
+ // from the trusted Coding plane at the moment of each observation and rechecked
113
+ // after the native read, never from a client-supplied permission object.
114
+ let codingPolicy=null;
115
+ const nativeObserveMode=process.env.DEADBYTE_R67_NATIVE_OBSERVE ?? '0';
116
+ if(!['0','1'].includes(nativeObserveMode))throw new Error('DEADBYTE_R67_NATIVE_OBSERVE must be 0 or 1');
117
+ const assertNativeObservationAuthority=createR67NativeObservationAuthority({
118
+ getCodingPolicy:()=>codingPolicy,enabled:nativeObserveMode==='1'
119
+ });
110
120
  const machineRuntimeMode = process.env.DEADBYTE_MACHINE_RUNTIME ?? '0';
111
121
  if (!['0', '1'].includes(machineRuntimeMode)) throw new Error('DEADBYTE_MACHINE_RUNTIME must be 0 or 1');
112
122
  planeRegistry.define('machine',{configured:machineRuntimeMode==='1',required:true});
113
- const machineRuntime=await planeRegistry.initialize('machine',async()=>createMachineRuntime({stateRoot:runtimeStateRoot}));
123
+ const machineRuntime=await planeRegistry.initialize('machine',async()=>createMachineRuntime({stateRoot:runtimeStateRoot,assertObservationAuthority:assertNativeObservationAuthority}));
114
124
  const agentRuntimeMode = process.env.DEADBYTE_AGENT_RUNTIME ?? '0';
115
125
  if (!['0', '1'].includes(agentRuntimeMode)) throw new Error('DEADBYTE_AGENT_RUNTIME must be 0 or 1');
116
126
  planeRegistry.define('agent',{configured:agentRuntimeMode==='1',required:false});
@@ -142,7 +152,6 @@ if (codingRuntimeMode === '1' && !codingPolicyPath) {
142
152
  catch (error) { if (error?.code !== 'ENOENT') throw error; }
143
153
  }
144
154
  planeRegistry.define('coding',{configured:codingRuntimeMode==='1',required:false});
145
- let codingPolicy=null;
146
155
  const codingRuntime=await planeRegistry.initialize('coding',async()=>{
147
156
  if(!codingPolicyPath) throw new Error('DEADBYTE_CODING_RUNTIME=1 requires a coding policy');
148
157
  codingPolicy=await loadCodingPolicy(codingPolicyPath);
@@ -0,0 +1,66 @@
1
+ import {spawnSync} from 'node:child_process';
2
+ import {createHash} from 'node:crypto';
3
+ import {lstat,readFile} from 'node:fs/promises';
4
+ import path from 'node:path';
5
+ import {fileURLToPath} from 'node:url';
6
+ import {signR67Observation} from './r67-observation-receipt.mjs';
7
+
8
+ const root=path.dirname(path.dirname(fileURLToPath(import.meta.url)));
9
+ const exe=path.join(root,'bin','deadbyte-native-inspect-r67.exe');
10
+ const metaFile=path.join(root,'docs','R67-NATIVE-INSPECT-BINARY.json');
11
+ const hash=b=>createHash('sha256').update(b).digest('hex');
12
+ const validLimit=(s,min,max)=>{if(!/^[1-9][0-9]*$/.test(s))throw new Error('invalid positive limit');const n=Number(s);if(!Number.isSafeInteger(n)||n<min||n>max)throw new Error('limit out of range');return String(n);};
13
+ export function nativeInspectArgs(rest){
14
+ const [kind,...args]=rest;
15
+ if(kind==='process'){if(args.length>1)throw new Error('inspect process [1..256]');return ['process',...(args.length?[validLimit(args[0],1,256)]:[])];}
16
+ if(kind==='identity'){
17
+ if(args.length!==1)throw new Error('inspect identity <PID>');
18
+ return ['identity',validLimit(args[0],1,4294967295)];
19
+ }
20
+ if(kind==='memory'){
21
+ if(args[0]==='self'){
22
+ if(args.length>2)throw new Error('inspect memory self [limit]');
23
+ return ['map','self',...(args.length===2?[validLimit(args[1],1,512)]:[])];
24
+ }
25
+ if(args.length<2||args.length>3)throw new Error('inspect memory PID <creation_filetime> [limit]');
26
+ const pid=validLimit(args[0],1,4294967295);
27
+ const token=args[1];
28
+ if(!/^[1-9][0-9]*$/.test(token)||BigInt(token)>18446744073709551615n)throw new Error('invalid process creation FILETIME');
29
+ return ['map',pid,token,...(args.length===3?[validLimit(args[2],1,512)]:[])];
30
+ }
31
+ if(kind==='pe'){
32
+ if(args.length!==1||!args[0]||args[0].includes('\0'))throw new Error('inspect pe <file>');
33
+ return ['pe',path.resolve(args[0])];
34
+ }
35
+ throw new Error('inspect expects process, memory, or pe');
36
+ }
37
+ export async function runNativeInspect(rest,{trustedSigner=null}={}){
38
+ if(process.platform!=='win32')throw new Error('native inspector requires win32');
39
+ const args=nativeInspectArgs(rest);
40
+ const meta=JSON.parse(await readFile(metaFile,'utf8'));
41
+ if(meta?.schema!=='deadbyte.r67-native-inspector-binary.v1'||meta.exe!=='bin/deadbyte-native-inspect-r67.exe'||!/^[a-f0-9]{64}$/.test(meta.sha256))throw new Error('R67 native executable authority invalid');
42
+ const info=await lstat(exe);
43
+ if(!info.isFile()||info.isSymbolicLink())throw new Error('R67 executable is not a regular non-symlink');
44
+ const before=hash(await readFile(exe));
45
+ if(before!==meta.sha256)throw new Error('R67 executable hash pin mismatch');
46
+ const result=spawnSync(exe,args,{encoding:'utf8',windowsHide:true,shell:false,timeout:15000,maxBuffer:2097152});
47
+ const after=hash(await readFile(exe));
48
+ if(after!==before)throw new Error('R67 executable drift after execution; outcome uncertain');
49
+ if(result.error)throw new Error('native inspector execution failed: '+result.error.message);
50
+ const stdout=String(result.stdout||'');
51
+ if(Buffer.byteLength(stdout,'utf8')>2097152)throw new Error('native inspector output exceeded limit');
52
+ let parsed;try{parsed=JSON.parse(stdout);}catch{throw new Error('native inspector output invalid JSON');}
53
+ if(parsed?.schema!=='deadbyte.native-inspect.r67.v1')throw new Error('native inspector output schema mismatch');
54
+ if(parsed.status==='ok'){
55
+ if(result.status!==0||parsed.operation!==args[0]||!parsed.data||parsed.data_sha256!==hash(Buffer.from(JSON.stringify(parsed.data),'utf8')))throw new Error('native inspector result digest mismatch');
56
+ }else if(parsed.status!=='failed'||result.status!==2||typeof parsed.error!=='string')throw new Error('native inspector failure contract mismatch');
57
+ if(trustedSigner!==null){
58
+ if(!trustedSigner||!trustedSigner.privateKey||!trustedSigner.publicKey)throw new Error('R67 signed observation requires an explicit trusted signer');
59
+ const receipt=signR67Observation({
60
+ ...trustedSigner,operation:args[0],requestSha256:hash(Buffer.from(JSON.stringify(args),'utf8')),
61
+ executableSha256:before,result:parsed
62
+ });
63
+ return {result:parsed,receipt};
64
+ }
65
+ return parsed;
66
+ }
@@ -0,0 +1,118 @@
1
+ import {createHash} from 'node:crypto';
2
+ import {lstat,readFile,readdir,open} from 'node:fs/promises';
3
+ import path from 'node:path';
4
+
5
+ const hash=bytes=>createHash('sha256').update(bytes).digest('hex');
6
+ const allowedLog=/^controller-(?:start|restart)\.(?:stdout|stderr)\.log$/;
7
+ const releasePattern=/^v[0-9]+\.[0-9]+\.[0-9]+-[a-f0-9]{16}$/;
8
+ const hex64=/^[a-f0-9]{64}$/;
9
+ const events=['reconnect','connected','disconnect','timeout','cancel','rollback','restore','revoked','ready','start','restart','failed','success'];
10
+ const EVENT_RX=new RegExp('\\b('+events.join('|')+')\\b','i');
11
+ const timestampRx=/^(\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d\.\d{3}Z)\b/;
12
+
13
+ export function redactDiagnosticLine(text){
14
+ if(typeof text!=='string'||text.length>8192)return null;
15
+ const level=/\b(ERROR|WARN|INFO|DEBUG)\b/i.exec(text)?.[1]?.toUpperCase();
16
+ const event=EVENT_RX.exec(text)?.[1]?.toLowerCase();
17
+ if(!level||!event)return null;
18
+ const result={};
19
+ const stamp=timestampRx.exec(text)?.[1];
20
+ if(stamp&&!Number.isNaN(Date.parse(stamp)))result.timestamp=stamp;
21
+ result.level=level;result.event=event;
22
+ const error=/\bwin32=(\d{1,5})\b/i.exec(text)?.[1];
23
+ if(error!==undefined&&Number(error)<=65535)result.win32_error=Number(error);
24
+ return result;
25
+ }
26
+ function crc32(data){
27
+ let crc=0xffffffff;
28
+ for(const value of data){
29
+ crc^=value;
30
+ for(let i=0;i<8;i++)crc=(crc>>>1)^((crc&1)?0xedb88320:0);
31
+ }
32
+ return (crc^0xffffffff)>>>0;
33
+ }
34
+ function zipStore(entries){
35
+ const locals=[],centrals=[];
36
+ let offset=0;
37
+ for(const [name,contents] of entries){
38
+ if(!/^[a-z0-9.-]+\.json$/.test(name))throw new Error('noncanonical archive entry');
39
+ const nameBytes=Buffer.from(name),data=Buffer.from(contents,'utf8'),crc=crc32(data);
40
+ if(data.length>1024*1024)throw new Error('archive payload too large');
41
+ const local=Buffer.alloc(30);
42
+ local.writeUInt32LE(0x04034b50,0);
43
+ local.writeUInt16LE(20,4);
44
+ local.writeUInt32LE(crc,14);
45
+ local.writeUInt32LE(data.length,18);local.writeUInt32LE(data.length,22);
46
+ local.writeUInt16LE(nameBytes.length,26);
47
+ locals.push(local,nameBytes,data);
48
+ const central=Buffer.alloc(46);
49
+ central.writeUInt32LE(0x02014b50,0);
50
+ central.writeUInt16LE(20,4);central.writeUInt16LE(20,6);
51
+ central.writeUInt32LE(crc,16);
52
+ central.writeUInt32LE(data.length,20);central.writeUInt32LE(data.length,24);
53
+ central.writeUInt16LE(nameBytes.length,28);
54
+ central.writeUInt32LE(offset,42);
55
+ centrals.push(central,nameBytes);
56
+ offset+=local.length+nameBytes.length+data.length;
57
+ }
58
+ const directory=Buffer.concat(centrals);
59
+ const end=Buffer.alloc(22);
60
+ end.writeUInt32LE(0x06054b50,0);
61
+ end.writeUInt16LE(entries.length,8);end.writeUInt16LE(entries.length,10);
62
+ end.writeUInt32LE(directory.length,12);end.writeUInt32LE(offset,16);
63
+ return Buffer.concat([...locals,directory,end]);
64
+ }
65
+ export function makeOfflineDiagnosticBundle({releaseId,manifestSha256,logs=[]}={}){
66
+ if(!releasePattern.test(releaseId??'')||!hex64.test(manifestSha256??''))throw new Error('trusted release identity invalid');
67
+ if(!Array.isArray(logs)||logs.length>8)throw new Error('log source budget exceeded');
68
+ const redacted=[];
69
+ const sources=new Set();
70
+ for(const source of logs){
71
+ if(!source||!allowedLog.test(source.source)||typeof source.content!=='string'||Buffer.byteLength(source.content)>65536||sources.has(source.source))
72
+ throw new Error('unsupported diagnostic source');
73
+ sources.add(source.source);
74
+ const lines=source.content.split(/\r?\n/).slice(0,512);
75
+ for(const line of lines){
76
+ if(redacted.length===256)break;
77
+ const record=redactDiagnosticLine(line);
78
+ if(record)redacted.push({source:source.source,...record});
79
+ }
80
+ }
81
+ const summary={schema:'deadbyte.r67-offline-diagnostics.v1',release_id:releaseId,release_manifest_sha256:manifestSha256,
82
+ network_upload:'NEVER',redaction:'FIXED_FIELDS_ONLY',source_count:sources.size,redacted_event_count:redacted.length,
83
+ raw_log_lines_exported:0,private_memory_exported:false};
84
+ const zip=zipStore([
85
+ ['diagnostics.json',JSON.stringify(summary,null,2)+'\n'],
86
+ ['redacted-events.json',JSON.stringify(redacted,null,2)+'\n']
87
+ ]);
88
+ return {zip,summary,archive_sha256:hash(zip)};
89
+ }
90
+ async function regularBounded(file,maxBytes){
91
+ const info=await lstat(file);
92
+ if(!info.isFile()||info.isSymbolicLink()||info.size>maxBytes)throw new Error('invalid or oversized diagnostic source');
93
+ const b=await readFile(file);
94
+ if(b.length>maxBytes)throw new Error('source size changed');
95
+ return b.toString('utf8');
96
+ }
97
+ export async function exportOfflineDiagnostics({stateRoot,outputPath,upload=false}={}){
98
+ if(upload!==false)throw new Error('network upload disabled unconditionally');
99
+ if(typeof outputPath!=='string'||!outputPath||/^[a-z]+:\/\//i.test(outputPath))throw new Error('explicit local outputPath required');
100
+ if(typeof stateRoot!=='string'||!stateRoot)throw new Error('local stateRoot required');
101
+ const root=path.resolve(stateRoot);
102
+ const pointer=JSON.parse(await regularBounded(path.join(root,'active-release.json'),131072));
103
+ const logDir=path.join(root,'runtime','logs');
104
+ let names=[];
105
+ try{names=(await readdir(logDir)).filter(n=>allowedLog.test(n)).sort().slice(0,8);}catch(error){if(error.code!=='ENOENT')throw error;}
106
+ const logs=[];
107
+ for(const name of names){
108
+ const file=path.join(logDir,name);
109
+ try{logs.push({source:name,content:await regularBounded(file,65536)});}
110
+ catch(error){if(error.code!=='ENOENT')throw error;}
111
+ }
112
+ const bundle=makeOfflineDiagnosticBundle({releaseId:pointer.release_id,manifestSha256:pointer.manifest_sha256,logs});
113
+ const target=path.resolve(outputPath);
114
+ const h=await open(target,'wx',0o600);
115
+ try{await h.writeFile(bundle.zip);await h.sync();}finally{await h.close();}
116
+ return {status:'written_local_only',uploaded:false,archive_sha256:bundle.archive_sha256,bytes:bundle.zip.length,
117
+ source_count:bundle.summary.source_count,redacted_event_count:bundle.summary.redacted_event_count};
118
+ }