deadbyte-mcp 0.15.61 → 0.15.63

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 (83) hide show
  1. package/MANIFEST.SHA256 +81 -32
  2. package/PROVENANCE.json +38 -11
  3. package/README.txt +18 -18
  4. package/RELEASE.json +59 -15
  5. package/SBOM.cdx.json +2 -2
  6. package/TEST-MATRIX.json +31 -2
  7. package/bridge/package.json +1 -1
  8. package/controller/deadbyte-controller.ps1 +78 -1
  9. package/controller/deadbyte-process-policy.json +4 -4
  10. package/docs/R156-LOOP-AND-EPOCH-ROUTING.md +86 -0
  11. package/docs/R157-ARCHITECTURE-DEPENDENCIES.md +58 -0
  12. package/docs/R157-ARCHITECTURE-GRAPH.json +2874 -0
  13. package/docs/R157-ENGINEERING-HANDOFF.md +71 -0
  14. package/docs/R157-FILE-DELTA.json +80 -0
  15. package/docs/R157-FOCUSED-VERIFICATION.log +0 -0
  16. package/docs/R157-REFERENCE-CAPABILITY-MATRIX.md +207 -0
  17. package/docs/R157-REFERENCE-DECISIONS.md +46 -0
  18. package/docs/R157-REFERENCE-INVENTORY.json +2849 -0
  19. package/docs/R158-COMBINED-REGRESSION.log +0 -0
  20. package/docs/R158-DELTA.json +80 -0
  21. package/docs/R158-EPOCH-BOOTSTRAP-GREEN.log +0 -0
  22. package/docs/R158-EPOCH-BOOTSTRAP-RED.log +0 -0
  23. package/docs/R158-EPOCH-IDENTITY-RED.log +0 -0
  24. package/docs/R158-FOCUSED-GREEN.log +0 -0
  25. package/docs/R158-PROTECTED-EPOCH-VERIFIED.md +43 -0
  26. package/docs/R158-PROTECTED-EXACT-SOURCE-LIVE.log +0 -0
  27. package/docs/R158-UPDATER-REGRESSION.log +0 -0
  28. package/docs/R159-COMPLETED-HISTORY-BACKUP/deadbyte-cli.mjs.before +965 -0
  29. package/docs/R159-COMPLETED-HISTORY-BACKUP/legacy-marker-evidence-r159.mjs.before +102 -0
  30. package/docs/R159-COMPLETED-HISTORY-BACKUP/r159-legacy-marker-evidence.test.mjs.before +79 -0
  31. package/docs/R159-COMPLETED-HISTORY-PATCH.json +26 -0
  32. package/docs/R159-MARKER-LOCATOR-CHECKPOINT.md +46 -0
  33. package/docs/R160-ENGINEERING-HANDOFF.md +32 -0
  34. package/docs/R160-REFERENCE-CAPABILITY-MATRIX.json +165 -0
  35. package/package.json +2 -2
  36. package/scripts/gate-windows-r160.ps1 +57 -0
  37. package/scripts/npm-release-r48.mjs +10 -0
  38. package/scripts/npm-release-readiness-r163.mjs +67 -0
  39. package/scripts/npm-release-readiness-r165.mjs +68 -0
  40. package/src/autonomous-supervisor.mjs +21 -4
  41. package/src/deadbyte-cli.mjs +90 -50
  42. package/src/legacy-marker-evidence-r159.mjs +128 -0
  43. package/src/package-identity-r160.mjs +61 -0
  44. package/src/release-generation.mjs +13 -0
  45. package/src/release-slot.mjs +2 -1
  46. package/src/remote-live-log.mjs +30 -2
  47. package/src/runtime-update.mjs +18 -6
  48. package/src/trusted-epoch-bootstrap-r158.mjs +42 -0
  49. package/src/version.mjs +1 -1
  50. package/src/windows-powershell-environment.mjs +5 -2
  51. package/test/r106-trusted-powershell-spawn.test.mjs +1 -0
  52. package/test/r130-release-security-contract.test.mjs +9 -9
  53. package/test/r132-release-identity.test.mjs +12 -10
  54. package/test/r153-prepublish-identity.test.mjs +25 -4
  55. package/test/r153-staging-leaf-acl.test.mjs +5 -2
  56. package/test/r155-commander-live-log.test.mjs +44 -0
  57. package/test/r155-default-live-log.test.mjs +89 -0
  58. package/test/r156-epoch-route-smoke.ps1 +48 -0
  59. package/test/r156-supervisor-pacing.test.mjs +59 -0
  60. package/test/r157-architecture-map.mjs +61 -0
  61. package/test/r157-reference-survey.mjs +91 -0
  62. package/test/r157-supervisor-singleflight.test.mjs +32 -0
  63. package/test/r158-epoch-bootstrap.test.mjs +34 -0
  64. package/test/r159-legacy-marker-evidence.test.mjs +103 -0
  65. package/test/r159-legacy-marker-evidence.test.mjs.before-assertion-fix-20261025-120246.bak +100 -0
  66. package/test/r160-marker-proof.test.mjs +87 -0
  67. package/test/r160-package-decision-manifest.test.mjs +67 -0
  68. package/test/r160-release-contract.test.mjs +30 -0
  69. package/test/r160-stable-launcher.test.mjs +10 -0
  70. package/test/r163-isolated-slot-rollback-restore.test.mjs +58 -0
  71. package/test/r163-publish-fence.test.mjs +13 -0
  72. package/test/r164-remote-history-skew.test.mjs +13 -0
  73. package/test/r165-live-cli-skew.test.mjs +90 -0
  74. package/test/r165-publish-fence.test.mjs +13 -0
  75. package/test/r48-release-contract.test.mjs +7 -1
  76. package/test/r58-launcher-shell.test.mjs +4 -1
  77. package/test/r59-tool-surface-compat.test.mjs +1 -1
  78. package/test/r65-cli-invocation.test.mjs +27 -7
  79. package/test/release-generation.test.mjs +16 -0
  80. package/test/remote-live-log-r37.test.mjs +5 -3
  81. package/test/remote-session-cli.test.mjs +11 -11
  82. package/test/stabilization-state-root.test.mjs +2 -1
  83. package/node_modules/which/CHANGELOG.md +0 -166
@@ -0,0 +1,67 @@
1
+ /** R163 0.15.62 publication fence. No evidence generation or authority mutation. */
2
+ import {createHash} from 'node:crypto';
3
+ import {readFile,lstat} from 'node:fs/promises';
4
+ import path from 'node:path';
5
+
6
+ const sha=b=>createHash('sha256').update(b).digest('hex');
7
+ const parent='v0.15.61-055d1ff0aa99e60b';
8
+ const parentManifest='055d1ff0aa99e60bbf9e9f136b02ebd4fffb8391712560f7607cff30af94dcdf';
9
+ const parentGen='f940e794daac2dda097a66301c4b85c4b29a2df7c669e549503c05a2a55ff6c4';
10
+ const reject=(m)=>{throw new Error('PUBLISH_BLOCKED R163 '+m);};
11
+ const hex=x=>typeof x==='string'&&/^[0-9a-f]{64}$/.test(x);
12
+ const readJson=async f=>JSON.parse(await readFile(f,'utf8'));
13
+ async function checkedEvidenceFile(file,digest,{allowEmpty=false}={}){
14
+ if(!hex(digest))reject('missing exact evidence digest');
15
+ const info=await lstat(file).catch(()=>reject('evidence file missing '+file));
16
+ if(!info.isFile()||info.isSymbolicLink()||(!allowEmpty&&info.size<1)||info.size>16*1024*1024)reject('invalid evidence file');
17
+ const bytes=await readFile(file);
18
+ if(sha(bytes)!==digest)reject('evidence SHA-256 mismatch '+file);
19
+ return bytes;
20
+ }
21
+ function boundedEvidence(root,relative){
22
+ if(typeof relative!=='string'||!relative||relative.includes('\\'))reject('invalid evidence relative path');
23
+ const p=path.resolve(root,relative),r=path.relative(root,p);
24
+ if(!r||r.startsWith('..')||path.isAbsolute(r))reject('evidence path escape');
25
+ return p;
26
+ }
27
+ export async function assertR163NpmPublishReady(release,{root,evidenceRoot=process.env.DEADBYTE_R163_CLOSURE_ROOT}={}){
28
+ if(release?.name!=='deadbyte-mcp'||release?.version!=='0.15.62'||release?.train!=='R160'||release?.r163?.publication_mode!=='PUBLISH_ONLY')reject('release metadata not R163-closed');
29
+ if(release?.parent?.release_id!==parent||release?.parent?.manifest_sha256!==parentManifest||release?.parent?.generation_manifest_sha256!==parentGen||release?.runtime_predecessor?.release_id!==parent)reject('active predecessor lineage mismatch');
30
+ if(!root||!path.isAbsolute(root))reject('candidate root missing');
31
+ if(!evidenceRoot||!path.isAbsolute(evidenceRoot))reject('closure evidence path missing');
32
+ const testRoot=path.resolve(root,'..','..','testing','r163-release-closure');
33
+ const evidence=path.resolve(evidenceRoot);
34
+ const relative=path.relative(testRoot,evidence);
35
+ if(!relative||relative.startsWith('..')||path.isAbsolute(relative))reject('evidence outside bounded R163 testing root');
36
+ for(const p of [testRoot,evidence]){const st=await lstat(p).catch(()=>reject('evidence directory absent'));if(!st.isDirectory()||st.isSymbolicLink())reject('evidence directory is reparse/symlink');}
37
+ const closurePath=path.join(evidence,'closure.json');
38
+ const closureInfo=await lstat(closurePath).catch(()=>reject('closure missing'));
39
+ if(!closureInfo.isFile()||closureInfo.isSymbolicLink()||closureInfo.size<1||closureInfo.size>1024*1024)reject('closure file not regular/bounded');
40
+ const closure=await readJson(closurePath).catch(()=>reject('closure missing/invalid'));
41
+ if(closure?.schema!=='deadbyte.r163-release-closure.v1'||closure?.version!=='0.15.62'||closure?.status!=='PASS'||closure?.production_release_id!==parent)reject('closure subject/status mismatch');
42
+ const manifestSha=sha(await readFile(path.join(root,'MANIFEST.SHA256')));
43
+ if(closure.source_manifest_sha256!==manifestSha)reject('final manifest differs from closure');
44
+ const files=closure?.files;
45
+ if(!files||!['protected_receipt','protected_stdout','protected_stderr','verify_receipt','verify_stdout','verify_stderr'].every(k=>files[k]&&typeof files[k].path==='string'&&hex(files[k].sha256)))reject('required receipts missing');
46
+ const data={};
47
+ for(const [key,info] of Object.entries(files)) data[key]=await checkedEvidenceFile(boundedEvidence(testRoot,info.path),info.sha256,{allowEmpty:key.endsWith('_stderr')});
48
+ let protectedReceipt,verifyReceipt;
49
+ try{protectedReceipt=JSON.parse(data.protected_receipt);verifyReceipt=JSON.parse(data.verify_receipt);}
50
+ catch{reject('gate receipts invalid JSON');}
51
+ if(protectedReceipt?.schema!=='deadbyte.r163-protected-windows-gate.v1'||protectedReceipt?.stage!=='full'||protectedReceipt?.exit_code!==0||protectedReceipt?.source_manifest_sha256!==manifestSha||protectedReceipt?.active_release_id!==parent)reject('protected gate missing or unbound');
52
+ if(protectedReceipt.stdout_sha256!==sha(data.protected_stdout)||protectedReceipt.stderr_sha256!==sha(data.protected_stderr))reject('protected log mismatch');
53
+ const stdout=data.protected_stdout.toString('utf8');
54
+ const counts=Object.fromEntries(['tests','pass','fail','skipped'].map(k=>[k,Number(new RegExp('^# '+k+' (\\d+)\\s*$','m').exec(stdout)?.[1]??NaN)]));
55
+ if(counts.tests<1951||counts.pass<1902||counts.fail!==0||counts.skipped!==49||counts.pass+counts.skipped!==counts.tests||/^not ok\s+\d+/m.test(stdout))reject('full regression totals mismatch');
56
+ if(!/^ok\s+\d+\s+-\s+R163 isolated immutable slot activates, rolls back, and restores 0\.15\.62/m.test(stdout))reject('R163 live rollback proof missing');
57
+ const skips=stdout.split(/\r?\n/).filter(x=>/^ok\s+\d+\s+-.*# SKIP/.test(x));
58
+ if(skips.length!==49||skips.some(x=>!/historical|R35 external semantic backend|POSIX|R100 POSIX/i.test(x)))reject('unexpected unclassified skipped test');
59
+ if(verifyReceipt?.schema!=='deadbyte.r163-release-verify.v1'||verifyReceipt?.status!=='PASS'||verifyReceipt?.version!=='0.15.62'||verifyReceipt?.source_manifest_sha256!==manifestSha||verifyReceipt?.production_release_id!==parent||verifyReceipt?.exit_code!==0)reject('verify-only receipt subject/status mismatch');
60
+ if(verifyReceipt.stdout_sha256!==sha(data.verify_stdout)||verifyReceipt.stderr_sha256!==sha(data.verify_stderr))reject('verify-only transcript hash mismatch');
61
+ const vout=data.verify_stdout.toString('utf8');
62
+ if(!vout.includes('VERIFY-ONLY PIPELINE PASS')||!vout.includes('Artifact isolation gate PASS')||!vout.includes('VERIFIED_TARBALL '))reject('npm verify-only output missing');
63
+ const tarLine=vout.split(/\r?\n/).find(x=>x.startsWith('VERIFIED_TARBALL '));
64
+ try{const p=JSON.parse(tarLine.slice('VERIFIED_TARBALL '.length));if(p.filename!=='deadbyte-mcp-0.15.62.tgz'||!hex(p.sha256)||p.bytes<1)reject('npm tarball identity invalid');}
65
+ catch{reject('npm tarball evidence invalid');}
66
+ return Object.freeze({status:'R163_RELEASE_READY_EVIDENCE_BOUND',version:'0.15.62',manifest_sha256:manifestSha,checks:6});
67
+ }
@@ -0,0 +1,68 @@
1
+ /** R165 0.15.63 publication fence. No evidence generation or authority mutation. */
2
+ import {createHash} from 'node:crypto';
3
+ import {readFile,lstat} from 'node:fs/promises';
4
+ import path from 'node:path';
5
+
6
+ const sha=b=>createHash('sha256').update(b).digest('hex');
7
+ const parent='v0.15.61-055d1ff0aa99e60b';
8
+ const parentManifest='055d1ff0aa99e60bbf9e9f136b02ebd4fffb8391712560f7607cff30af94dcdf';
9
+ const parentGen='f940e794daac2dda097a66301c4b85c4b29a2df7c669e549503c05a2a55ff6c4';
10
+ const reject=(m)=>{throw new Error('PUBLISH_BLOCKED R165 '+m);};
11
+ const hex=x=>typeof x==='string'&&/^[0-9a-f]{64}$/.test(x);
12
+ const readJson=async f=>JSON.parse(await readFile(f,'utf8'));
13
+ async function checkedEvidenceFile(file,digest,{allowEmpty=false}={}){
14
+ if(!hex(digest))reject('missing exact evidence digest');
15
+ const info=await lstat(file).catch(()=>reject('evidence file missing '+file));
16
+ if(!info.isFile()||info.isSymbolicLink()||(!allowEmpty&&info.size<1)||info.size>16*1024*1024)reject('invalid evidence file');
17
+ const bytes=await readFile(file);
18
+ if(sha(bytes)!==digest)reject('evidence SHA-256 mismatch '+file);
19
+ return bytes;
20
+ }
21
+ function boundedEvidence(root,relative){
22
+ if(typeof relative!=='string'||!relative||relative.includes('\\'))reject('invalid evidence relative path');
23
+ const p=path.resolve(root,relative),r=path.relative(root,p);
24
+ if(!r||r.startsWith('..')||path.isAbsolute(r))reject('evidence path escape');
25
+ return p;
26
+ }
27
+ export async function assertR165NpmPublishReady(release,{root,evidenceRoot=process.env.DEADBYTE_R165_CLOSURE_ROOT}={}){
28
+ if(release?.name!=='deadbyte-mcp'||release?.version!=='0.15.63'||release?.train!=='R165'||release?.r165?.publication_mode!=='PUBLISH_ONLY')reject('release metadata not R165-closed');
29
+ if(release?.parent?.release_id!==parent||release?.parent?.manifest_sha256!==parentManifest||release?.parent?.generation_manifest_sha256!==parentGen||release?.runtime_predecessor?.release_id!==parent)reject('active predecessor lineage mismatch');
30
+ if(!root||!path.isAbsolute(root))reject('candidate root missing');
31
+ if(!evidenceRoot||!path.isAbsolute(evidenceRoot))reject('closure evidence path missing');
32
+ const testRoot=path.resolve(root,'..','..','testing','r165-release-closure');
33
+ const evidence=path.resolve(evidenceRoot);
34
+ const relative=path.relative(testRoot,evidence);
35
+ if(!relative||relative.startsWith('..')||path.isAbsolute(relative))reject('evidence outside bounded R165 testing root');
36
+ for(const p of [testRoot,evidence]){const st=await lstat(p).catch(()=>reject('evidence directory absent'));if(!st.isDirectory()||st.isSymbolicLink())reject('evidence directory is reparse/symlink');}
37
+ const closurePath=path.join(evidence,'closure.json');
38
+ const closureInfo=await lstat(closurePath).catch(()=>reject('closure missing'));
39
+ if(!closureInfo.isFile()||closureInfo.isSymbolicLink()||closureInfo.size<1||closureInfo.size>1024*1024)reject('closure file not regular/bounded');
40
+ const closure=await readJson(closurePath).catch(()=>reject('closure missing/invalid'));
41
+ if(closure?.schema!=='deadbyte.r165-release-closure.v1'||closure?.version!=='0.15.63'||closure?.status!=='PASS'||closure?.production_release_id!==parent)reject('closure subject/status mismatch');
42
+ const manifestSha=sha(await readFile(path.join(root,'MANIFEST.SHA256')));
43
+ if(closure.source_manifest_sha256!==manifestSha)reject('final manifest differs from closure');
44
+ const files=closure?.files;
45
+ if(!files||!['protected_receipt','protected_stdout','protected_stderr','verify_receipt','verify_stdout','verify_stderr'].every(k=>files[k]&&typeof files[k].path==='string'&&hex(files[k].sha256)))reject('required receipts missing');
46
+ const data={};
47
+ for(const [key,info] of Object.entries(files)) data[key]=await checkedEvidenceFile(boundedEvidence(testRoot,info.path),info.sha256,{allowEmpty:key.endsWith('_stderr')});
48
+ let protectedReceipt,verifyReceipt;
49
+ try{protectedReceipt=JSON.parse(data.protected_receipt);verifyReceipt=JSON.parse(data.verify_receipt);}
50
+ catch{reject('gate receipts invalid JSON');}
51
+ if(protectedReceipt?.schema!=='deadbyte.r165-protected-windows-gate.v1'||protectedReceipt?.stage!=='full'||protectedReceipt?.exit_code!==0||protectedReceipt?.source_manifest_sha256!==manifestSha||protectedReceipt?.active_release_id!==parent)reject('protected gate missing or unbound');
52
+ if(protectedReceipt.stdout_sha256!==sha(data.protected_stdout)||protectedReceipt.stderr_sha256!==sha(data.protected_stderr))reject('protected log mismatch');
53
+ const stdout=data.protected_stdout.toString('utf8');
54
+ const counts=Object.fromEntries(['tests','pass','fail','skipped'].map(k=>[k,Number(new RegExp('^# '+k+' (\\d+)\\s*$','m').exec(stdout)?.[1]??NaN)]));
55
+ if(counts.tests<1955||counts.pass<1906||counts.fail!==0||counts.skipped!==49||counts.pass+counts.skipped!==counts.tests||/^not ok\s+\d+/m.test(stdout))reject('full regression totals mismatch');
56
+ if(!/^ok\s+\d+\s+-\s+R165 isolated immutable slot activates, rolls back, and restores 0\.15\.63/m.test(stdout))reject('R165 live rollback proof missing');
57
+ if(!/^ok\s+\d+\s+-\s+R165 executable CLI passes completed R159 history/m.test(stdout))reject('R165 executable startup proof missing');
58
+ const skips=stdout.split(/\r?\n/).filter(x=>/^ok\s+\d+\s+-.*# SKIP/.test(x));
59
+ if(skips.length!==49||skips.some(x=>!/historical|R35 external semantic backend|POSIX|R100 POSIX/i.test(x)))reject('unexpected unclassified skipped test');
60
+ if(verifyReceipt?.schema!=='deadbyte.r165-release-verify.v1'||verifyReceipt?.status!=='PASS'||verifyReceipt?.version!=='0.15.63'||verifyReceipt?.source_manifest_sha256!==manifestSha||verifyReceipt?.production_release_id!==parent||verifyReceipt?.exit_code!==0)reject('verify-only receipt subject/status mismatch');
61
+ if(verifyReceipt.stdout_sha256!==sha(data.verify_stdout)||verifyReceipt.stderr_sha256!==sha(data.verify_stderr))reject('verify-only transcript hash mismatch');
62
+ const vout=data.verify_stdout.toString('utf8');
63
+ if(!vout.includes('VERIFY-ONLY PIPELINE PASS')||!vout.includes('Artifact isolation gate PASS')||!vout.includes('VERIFIED_TARBALL '))reject('npm verify-only output missing');
64
+ const tarLine=vout.split(/\r?\n/).find(x=>x.startsWith('VERIFIED_TARBALL '));
65
+ try{const p=JSON.parse(tarLine.slice('VERIFIED_TARBALL '.length));if(p.filename!=='deadbyte-mcp-0.15.63.tgz'||!hex(p.sha256)||p.bytes<1)reject('npm tarball identity invalid');}
66
+ catch{reject('npm tarball evidence invalid');}
67
+ return Object.freeze({status:'R165_RELEASE_READY_EVIDENCE_BOUND',version:'0.15.63',manifest_sha256:manifestSha,checks:6});
68
+ }
@@ -24,7 +24,7 @@ async function listGoalIds(policy) {
24
24
  export function startAutonomousSupervisor({ runtime, policy = runtime?.policy,
25
25
  idleDelayMs = 750, maxCycles = 32, logger = console, continuous = true } = {}) {
26
26
  if (!runtime || !policy) throw new Error('autonomous supervisor requires runtime + policy');
27
- const providerBacked = ['openai_compatible','openai_responses'].includes(policy.planner.backend);
27
+ const providerBacked = ['openai_compatible','openai_responses','codex_app_server'].includes(policy.planner.backend);
28
28
  if (!Number.isInteger(idleDelayMs) || idleDelayMs < 25 || idleDelayMs > 60_000) {
29
29
  throw new Error('autonomous supervisor idleDelayMs out of range');
30
30
  }
@@ -41,6 +41,10 @@ export function startAutonomousSupervisor({ runtime, policy = runtime?.policy,
41
41
  let runnable = 0;
42
42
  for (const goalId of await listGoalIds(policy)) {
43
43
  if (abort.signal.aborted) break;
44
+ // A known retry deadline is authoritative until elapsed. Avoid
45
+ // polling goal state again just to rediscover the same backoff.
46
+ const pendingRetry=failures.get(goalId);
47
+ if (pendingRetry && Date.now() < pendingRetry.retry_at) continue;
44
48
  let status;
45
49
  try { status = await runtime.status({ goalId }); }
46
50
  catch (error) { logger.warn?.(`DEADBYTE supervisor status ${goalId}: ${String(error)}`); continue; }
@@ -61,9 +65,10 @@ export function startAutonomousSupervisor({ runtime, policy = runtime?.policy,
61
65
  failures.delete(goalId); continue;
62
66
  }
63
67
  }
64
- runnable += 1;
65
68
  const failure = failures.get(goalId);
66
69
  if (failure && Date.now() < failure.retry_at) continue;
70
+ // Only an eligible dispatch is runnable. Backoff is not useful work.
71
+ runnable += 1;
67
72
  try {
68
73
  const result = await runtime.run({ goalId, maxCycles });
69
74
  if (result.status === 'paused' && result.reason === 'provider_circuit_open') {
@@ -89,10 +94,22 @@ export function startAutonomousSupervisor({ runtime, policy = runtime?.policy,
89
94
  return runnable;
90
95
  }
91
96
 
97
+ // One supervisor, one in-flight scan. A manual scan and the polling
98
+ // loop must never race to dispatch the same durable goal.
99
+ let inFlightPass=null;
100
+ function scanOnce(){
101
+ if(inFlightPass)return inFlightPass;
102
+ const task=pass();
103
+ inFlightPass=task;
104
+ const clear=()=>{if(inFlightPass===task)inFlightPass=null;};
105
+ task.then(clear,clear);
106
+ return task;
107
+ }
108
+
92
109
  const done = continuous ? (async () => {
93
110
  while (!abort.signal.aborted) {
94
111
  let runnable = 0;
95
- try { runnable = await pass(); }
112
+ try { runnable = await scanOnce(); }
96
113
  catch (error) { logger.error?.(`DEADBYTE supervisor scan failed: ${String(error)}`); }
97
114
  if (abort.signal.aborted) break;
98
115
  await delay(runnable > 0 ? 25 : idleDelayMs, abort.signal);
@@ -103,7 +120,7 @@ export function startAutonomousSupervisor({ runtime, policy = runtime?.policy,
103
120
  get running() { return !abort.signal.aborted; },
104
121
  get passes() { return passes; },
105
122
  failures,
106
- async scanNow() { return pass(); },
123
+ async scanNow() { return scanOnce(); },
107
124
  async stop() { abort.abort(); await done; }
108
125
  });
109
126
  }
@@ -16,7 +16,7 @@ import { exportOfflineDiagnostics } from './r67-diagnostic-export.mjs';
16
16
  import { verifyOfflineDiagnosticsZip } from './r67-diagnostics-verify.mjs';
17
17
  import { parseActivePointer } from './release-slot.mjs';
18
18
  import { printDoctor } from './doctor-r45.mjs';
19
- import { classifyRemoteAuditOutcome, createLiveTimelineFilter, createLiveTimelineTracker, createLiveTimelineWriter, formatCompactAuditEvent, formatRemoteLifecycle, formatRemoteToolCall, formatRuntimeDiagnosticLine, selectLiveLogNames } from './remote-live-log.mjs';
19
+ import { classifyRemoteAuditOutcome, createLiveTimelineFilter, createLiveTimelineTracker, createLiveTimelineWriter, defaultForegroundTimelineOptions, formatRemoteLogResetNotice, formatCompactAuditEvent, formatRemoteLifecycle, formatRemoteToolCall, formatRuntimeDiagnosticLine, selectLiveLogNames } from './remote-live-log.mjs';
20
20
  import { prepareRemoteConsole, remoteDisplayText } from './remote-console.mjs';
21
21
  import { ensureRuntimeCoherence } from './runtime-coherence.mjs';
22
22
  import { windowsPowerShellEnvironment } from './runtime-update.mjs';
@@ -27,7 +27,9 @@ import {acquireForegroundSessionLock} from './foreground-session-lock.mjs';
27
27
  import { AUTHORITY_PLANES, authorityVector, authorityRestorePlan, parseAuthorityObservation, assertAuthorityVector, assertRuntimeReadyObservation, authorityBanner } from './remote-authority.mjs';
28
28
  import { decodeRemoteLogBytes, boundedRemoteDiagnostic, readRemoteLogAppend } from './remote-log-decode-r60.mjs';
29
29
  import {reconcileStaleMarker,resumeMarkerReconciliation,recoverPublishedMarkerTerminal} from './clean-activation-recovery-r153.mjs';
30
+ import {selectLegacyMarkerEvidence,verifyLocatedLegacyMarkerReceipt} from './legacy-marker-evidence-r159.mjs';
30
31
  import {normalizeHostCommandPins,encodeHostCommandPins} from './host-command-pins-r154.mjs';
32
+ import {verifyPackageDecisionManifest} from './package-identity-r160.mjs';
31
33
 
32
34
  const REMOTE_COMMANDS=new Set([
33
35
  'menu','start','resume','park','stop','restart','status','url','test','fulltest','toggle','clean','machinetest',
@@ -63,6 +65,7 @@ function printHelp(){
63
65
  ` npx -y ${exact} remote --full # explicit five-plane foreground session`,
64
66
  ` npx -y ${exact} remote --resume <cursor> # replay missed results, then reconnect`,
65
67
  ` npx -y ${exact} remote --trace # show trace/span identity without payload detail`,
68
+
66
69
  ` npx -y ${exact} remote --filter <expr> # presentation filter: tool/category/status/trace`,
67
70
  ` npx -y ${exact} remote status`,
68
71
  ` npx -y ${exact} remote restart`,
@@ -396,7 +399,7 @@ function timelineFilterEvent(timeline){
396
399
  async function tailRemoteLogs(root,signal,{journal=null,offsets=null,activityTracker=null,timelineFilter=null,timelineWriter=null,verbose=false,displayMode='unicode'}={}){
397
400
  process.stdout.write(verbose
398
401
  ? '[LOG] Verbose live timeline. Full sanitized request detail enabled; Ctrl+C disconnects and revokes authority.\n'
399
- : '[LOG] Human live timeline. Transport IN/OUT hidden; full replay/audit detail remains on disk. START/DONE retained; heartbeat bursts folded; Ctrl+C disconnects and revokes authority.\n');
402
+ : '[LOG] Live tool calls with individual START/DONE, sanitized arguments and results. Transport hidden; signed audit remains on disk. Ctrl+C disconnects and revokes authority.\n');
400
403
  const activeOffsets=offsets??await snapshotRemoteLogOffsets(root);
401
404
  const partialLines=new Map();
402
405
  let liveFiles=[...activeOffsets.keys()];
@@ -413,7 +416,7 @@ async function tailRemoteLogs(root,signal,{journal=null,offsets=null,activityTra
413
416
  activeOffsets.set(file,next.cursor);
414
417
  if(next.reset){
415
418
  partialLines.delete(file);
416
- process.stderr.write('[LIVELOG] STREAM_RESET '+path.basename(file)+' — '+next.resetReason+'; check audit continuity before trusting coverage\n');
419
+ process.stderr.write(formatRemoteLogResetNotice(file,next.resetReason)+'\n');
417
420
  }
418
421
  if(!next.text)continue;
419
422
  const lines=((partialLines.get(file)??'')+next.text).split(/\r?\n/);
@@ -513,64 +516,99 @@ async function publishExclusiveJson(file,value){
513
516
  }
514
517
  return {path:file,sha256:createHash('sha256').update(bytes).digest('hex')};
515
518
  }
519
+ async function readMarkerRecoveryAuthority(ctx,active){
520
+ const grantField={process:'grant_file',coding:'lease_file',autonomous:'lease_file',desktop:'grant_file',host:'arm_file'};
521
+ const policySha256={},grantPresence={};
522
+ for(const plane of AUTHORITY_PLANES){
523
+ const policyFile=path.join(active.generation_root,`${plane}-policy.json`),info=await lstat(policyFile);
524
+ if(!info.isFile()||info.isSymbolicLink()||path.resolve(path.dirname(policyFile)).toLowerCase()!==path.resolve(active.generation_root).toLowerCase())
525
+ throw new Error(`marker reconciliation ${plane} policy identity invalid`);
526
+ const bytes=await readFile(policyFile),policy=JSON.parse(bytes.toString('utf8').replace(/^\uFEFF/,''));
527
+ policySha256[plane]=createHash('sha256').update(bytes).digest('hex');
528
+ const grant=policy?.[grantField[plane]];
529
+ if(typeof grant!=='string'||!path.isAbsolute(grant)||
530
+ path.resolve(path.dirname(grant)).toLowerCase()!==path.resolve(ctx.root,'runtime').toLowerCase())
531
+ throw new Error(`marker reconciliation ${plane} grant path invalid`);
532
+ try{await lstat(grant);grantPresence[plane]=true;}
533
+ catch(error){if(error?.code==='ENOENT')grantPresence[plane]=false;else throw error;}
534
+ }
535
+ return {policySha256,grantPresence};
536
+ }
516
537
  async function recoverRemoteSession(ctx,{displayMode='unicode'}={}){
517
538
  let prior=await readRemoteSessionMarker(ctx.root);
518
539
  const evidenceDir=path.join(ctx.root,'evidence','legacy-migration');
519
- const locatorPath=path.join(evidenceDir,'r153-pending-migration.json');
520
540
  if(!prior){
521
- try{
522
- const locator=JSON.parse(await readFile(locatorPath,'utf8'));
523
- if(locator?.schema!=='deadbyte.r153-pending-migration.v1'||
524
- typeof locator.receipt_path!=='string'||!path.isAbsolute(locator.receipt_path)||
525
- path.resolve(path.dirname(locator.receipt_path)).toLowerCase()!==path.resolve(evidenceDir).toLowerCase()||
526
- !/^[0-9a-f]{64}$/.test(String(locator.receipt_sha256??'')))throw new Error('pending migration locator invalid');
527
- const bytes=await readFile(locator.receipt_path);
528
- if(createHash('sha256').update(bytes).digest('hex')!==locator.receipt_sha256)
529
- throw new Error('pending migration receipt hash mismatch');
530
- return {state:'MARKER_RECONCILED',receipt_path:locator.receipt_path,
531
- receipt_sha256:locator.receipt_sha256};
532
- }catch(error){if(error?.code!=='ENOENT')throw error;}
541
+ const activePointer=await readActiveRuntimeBinding(ctx.root);
542
+ // A historical terminal is not an uncompleted migration. Ignore only
543
+ // when the installed release identity matches the CLI's manifest identity;
544
+ // this path grants no authority and publishes no recovery evidence.
545
+ const currentManifestSha256=createHash('sha256').update(
546
+ await readFile(path.join(CURRENT_PACKAGE_ROOT,'MANIFEST.SHA256'))).digest('hex');
547
+ const historyAllowed=activePointer?.version===DEADBYTE_VERSION&&
548
+ activePointer?.manifest_sha256===currentManifestSha256&&
549
+ activePointer?.release_id===('v'+DEADBYTE_VERSION+'-'+currentManifestSha256.slice(0,16));
550
+ // R164: a different CLI version is not sufficient reason to replay a
551
+ // completed receipt. Only a verified ONLINE active predecessor may ignore
552
+ // completed history; pending, locator conflicts, and tampering still fail.
553
+ let onlinePredecessorReady=false;
554
+ if(!historyAllowed&&activePointer){
555
+ try{
556
+ const runtime=observedAuthority(ctx);
557
+ if(runtime.state==='ONLINE'){
558
+ assertRuntimeReadyObservation(runtime,{coreRoot:activePointer.release_root});
559
+ onlinePredecessorReady=true;
560
+ }
561
+ }catch{onlinePredecessorReady=false;}
562
+ }
563
+ const selected=await selectLegacyMarkerEvidence({evidenceDir,activePointer,
564
+ allowCompletedHistory:historyAllowed||onlinePredecessorReady});
565
+ if(selected.kind==='none')return null;
566
+ if(selected.kind==='completed_history_ambiguous'){
567
+ if(!historyAllowed){
568
+ if(!onlinePredecessorReady)throw new Error('R164 completed history requires a verified ONLINE predecessor');
569
+ const readback=await readActiveRuntimeBinding(ctx.root);
570
+ for(const key of ['release_id','version','manifest_sha256','generation_manifest_sha256'])
571
+ if(readback?.[key]!==activePointer?.[key])
572
+ throw new Error('R164 predecessor changed during completed-history classification');
573
+ assertRuntimeReadyObservation(observedAuthority(ctx),{coreRoot:readback.release_root});
574
+ }
575
+ // Every completed receipt was validated by R159 and no pending intent
576
+ // exists. Do not adopt any receipt as an upgrade/migration authority.
577
+ return null;
578
+ }
579
+ if(selected.kind==='located'){
580
+ const readback=await readActiveRuntimeBinding(ctx.root);
581
+ for(const key of ['release_id','version','manifest_sha256','generation_manifest_sha256'])
582
+ if(readback?.[key]!==activePointer?.[key])
583
+ throw new Error('R159 active release changed during located marker verification');
584
+ const {policySha256}=await readMarkerRecoveryAuthority(ctx,readback);
585
+ const verified=await verifyLocatedLegacyMarkerReceipt({located:selected,pointer:readback,policySha256});
586
+ const finalPointer=await readActiveRuntimeBinding(ctx.root);
587
+ for(const key of ['release_id','version','manifest_sha256','generation_manifest_sha256'])
588
+ if(finalPointer?.[key]!==readback?.[key])
589
+ throw new Error('R160 active release changed after located marker verification');
590
+ return verified;
591
+ }
592
+ // Completed historical receipts are not a pending migration for an
593
+ // already-online current release. Keep bytes and their SHA untouched.
594
+ if(selected.kind==='completed'&&observedAuthority(ctx).state==='ONLINE')return null;
595
+ if(selected.kind==='pending'&&observedAuthority(ctx).state!=='OFFLINE')
596
+ throw new Error('R159 pending marker reconciliation requires an offline runtime');
533
597
  // Crash recovery for the narrow window after marker CAS deletion and
534
598
  // before terminal receipt publication. Never infer completion from the
535
599
  // missing marker alone: require exactly one durable intent and re-observe
536
600
  // the full predecessor/policy/grant/runtime subject.
537
- let entries=[];
538
- try{entries=await readdir(evidenceDir,{withFileTypes:true});}
539
- catch(error){if(error?.code==='ENOENT')return null;throw error;}
540
- const candidates=[];
541
- for(const entry of entries){
542
- const match=/^marker-([0-9a-f]{64})\.intent\.json$/.exec(entry.name);
543
- if(!match||!entry.isFile())continue;
544
- const terminal=path.join(evidenceDir,`marker-${match[1]}.terminal.json`);
545
- let terminalExists=true;
546
- try{await lstat(terminal);}
547
- catch(error){if(error?.code==='ENOENT')terminalExists=false;else throw error;}
548
- candidates.push({markerSha256:match[1],intent:path.join(evidenceDir,entry.name),terminal,terminalExists});
549
- }
550
- if(candidates.length===0)return null;
551
- if(candidates.length!==1)throw new Error('multiple unlocated marker reconciliation claims; manual reconciliation required');
552
- const pending=candidates[0],intentInfo=await lstat(pending.intent);
601
+ const pending=selected.pending,intentInfo=await lstat(selected.pending.intent);
553
602
  if(!intentInfo.isFile()||intentInfo.isSymbolicLink())throw new Error('marker reconciliation intent is not a regular file');
554
603
  const intentBytes=await readFile(pending.intent),intent=JSON.parse(intentBytes.toString('utf8').replace(/^\uFEFF/,''));
555
604
  if(intent.marker_sha256!==pending.markerSha256)throw new Error('marker reconciliation intent filename identity mismatch');
556
605
  const active=await readActiveRuntimeBinding(ctx.root);
557
606
  if(!active)throw new Error('marker reconciliation predecessor pointer missing');
607
+ for(const key of ['release_id','version','manifest_sha256','generation_manifest_sha256'])
608
+ if(active[key]!==activePointer?.[key])
609
+ throw new Error('R159 active release changed during marker recovery');
558
610
  const observed=observedAuthority(ctx);
559
- const grantField={process:'grant_file',coding:'lease_file',autonomous:'lease_file',desktop:'grant_file',host:'arm_file'};
560
- const policySha256={},grantPresence={};
561
- for(const plane of AUTHORITY_PLANES){
562
- const policyFile=path.join(active.generation_root,`${plane}-policy.json`),info=await lstat(policyFile);
563
- if(!info.isFile()||info.isSymbolicLink()||path.resolve(path.dirname(policyFile)).toLowerCase()!==path.resolve(active.generation_root).toLowerCase())
564
- throw new Error(`marker reconciliation ${plane} policy identity invalid`);
565
- const bytes=await readFile(policyFile),policy=JSON.parse(bytes.toString('utf8').replace(/^\uFEFF/,''));
566
- policySha256[plane]=createHash('sha256').update(bytes).digest('hex');
567
- const grant=policy?.[grantField[plane]];
568
- if(typeof grant!=='string'||!path.isAbsolute(grant)||
569
- path.resolve(path.dirname(grant)).toLowerCase()!==path.resolve(ctx.root,'runtime').toLowerCase())
570
- throw new Error(`marker reconciliation ${plane} grant path invalid`);
571
- try{await lstat(grant);grantPresence[plane]=true;}
572
- catch(error){if(error?.code==='ENOENT')grantPresence[plane]=false;else throw error;}
573
- }
611
+ const {policySha256,grantPresence}=await readMarkerRecoveryAuthority(ctx,active);
574
612
  if(await readRemoteMarkerSnapshot(remoteSessionMarker(ctx.root))!==null)
575
613
  throw new Error('marker reconciliation marker reappeared');
576
614
  const intentSha256=createHash('sha256').update(intentBytes).digest('hex');
@@ -589,7 +627,7 @@ async function recoverRemoteSession(ctx,{displayMode='unicode'}={}){
589
627
  }
590
628
  const locator={schema:'deadbyte.r153-pending-migration.v1',receipt_path:pending.terminal,
591
629
  receipt_sha256:reconciled.receipt_sha256,marker_sha256:reconciled.marker_sha256};
592
- await publishExclusiveJson(locatorPath,locator);
630
+ await publishExclusiveJson(path.join(evidenceDir,'marker-'+reconciled.marker_sha256+'.locator.json'),locator);
593
631
  writeRemote(process.stdout,`${remoteTag('recovery',displayMode)} Interrupted marker reconciliation completed (${reconciled.marker_sha256.slice(0,12)}).\n`,displayMode);
594
632
  return {...reconciled,receipt_path:pending.terminal};
595
633
  }
@@ -692,7 +730,7 @@ async function recoverRemoteSession(ctx,{displayMode='unicode'}={}){
692
730
  deleteIfHash:async expected=>deletePinnedRemoteMarker(markerPath,expected)});
693
731
  const locator={schema:'deadbyte.r153-pending-migration.v1',receipt_path:terminalPath,
694
732
  receipt_sha256:reconciled.receipt_sha256,marker_sha256:reconciled.marker_sha256};
695
- await publishExclusiveJson(locatorPath,locator);
733
+ await publishExclusiveJson(path.join(evidenceDir,'marker-'+reconciled.marker_sha256+'.locator.json'),locator);
696
734
  writeRemote(process.stdout,`${remoteTag('recovery',displayMode)} Stale session evidence reconciled (${reconciled.marker_sha256.slice(0,12)}).\n`,displayMode);
697
735
  return {...reconciled,receipt_path:terminalPath};
698
736
  }
@@ -754,6 +792,8 @@ async function runRemoteSession({resumeCursor=null,verbose=false,trace=false,fil
754
792
  const startupLock=await acquireForegroundSessionLock({root:ctx.root,powershell:ctx.powershell});
755
793
  let marker;
756
794
  try{
795
+ startupLock.assertHeld();
796
+ await verifyPackageDecisionManifest({root:CURRENT_PACKAGE_ROOT,expectedVersion:DEADBYTE_VERSION});
757
797
  startupLock.assertHeld();
758
798
  const migrationContext=await recoverRemoteSession(ctx,{displayMode});
759
799
  startupLock.assertHeld();
@@ -766,7 +806,7 @@ async function runRemoteSession({resumeCursor=null,verbose=false,trace=false,fil
766
806
  filePath:remoteResultJournalPath(ctx.root,marker.result_session_id),sessionId:marker.result_session_id,maxEntries:2048
767
807
  });
768
808
  const rootPaths=await readLiveRootPaths(ctx.root);
769
- const activityTracker=createLiveTimelineTracker({verbose,displayMode,rootPaths,trace,foldHeartbeats:!verbose});
809
+ const activityTracker=createLiveTimelineTracker({verbose,displayMode,rootPaths,trace,...defaultForegroundTimelineOptions({verbose})});
770
810
  const timelineFilter=filterExpression?createLiveTimelineFilter(filterExpression):null;
771
811
  const timelineWriter=createLiveTimelineWriter({stream:process.stdout,displayMode,rewrite:false});
772
812
  const aborter=new AbortController();
@@ -0,0 +1,128 @@
1
+ import {readdir,lstat,readFile} from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import {createHash} from 'node:crypto';
4
+ import {verifyMarkerReconciliationReceipt} from './clean-activation-recovery-r153.mjs';
5
+
6
+ const H=/^[0-9a-f]{64}$/;
7
+ const digest=b=>createHash('sha256').update(b).digest('hex');
8
+ function samePredecessor(a,b){
9
+ return !!a&&!!b&&['release_id','version','manifest_sha256','generation_manifest_sha256']
10
+ .every(k=>typeof a[k]==='string'&&a[k]===b[k]);
11
+ }
12
+ async function regular(file){
13
+ const st=await lstat(file);
14
+ if(!st.isFile()||st.isSymbolicLink())throw new Error('R159 evidence file must be regular and not a link');
15
+ }
16
+ function strictJson(bytes,label){
17
+ let result;
18
+ try{result=JSON.parse(bytes.toString('utf8').replace(/^\uFEFF/,''));}
19
+ catch{throw new Error('R159 '+label+' invalid JSON');}
20
+ if(!result||typeof result!=='object'||Array.isArray(result))
21
+ throw new Error('R159 '+label+' invalid object');
22
+ return result;
23
+ }
24
+ // Examine ONLY local immutable receipt candidates. It must never infer a live
25
+ // process state, delete a marker, grant an authority plane, or mutate evidence.
26
+ export async function selectLegacyMarkerEvidence({evidenceDir,activePointer,allowCompletedHistory=false}){
27
+ if(typeof evidenceDir!=='string'||!path.isAbsolute(evidenceDir))
28
+ throw new Error('R159 evidence root must be absolute');
29
+ let entries;
30
+ try{entries=await readdir(evidenceDir,{withFileTypes:true});}
31
+ catch(e){if(e?.code==='ENOENT')return {kind:'none'};throw e;}
32
+ if(entries.length>4096)throw new Error('R159 evidence inventory exceeds bounded scan');
33
+ const fileSet=new Map(entries.map(e=>[e.name,e]));
34
+ const locatorPath=path.join(evidenceDir,'r153-pending-migration.json');
35
+ let located=null;
36
+ const locatorEntry=fileSet.get('r153-pending-migration.json');
37
+ if(locatorEntry){
38
+ if(!locatorEntry.isFile()||locatorEntry.isSymbolicLink())
39
+ throw new Error('R159 locator is not regular');
40
+ await regular(locatorPath);
41
+ const locator=strictJson(await readFile(locatorPath),'legacy locator');
42
+ if(locator.schema!=='deadbyte.r153-pending-migration.v1'||
43
+ !H.test(String(locator.marker_sha256??''))||
44
+ !H.test(String(locator.receipt_sha256??''))||
45
+ typeof locator.receipt_path!=='string')
46
+ throw new Error('R159 legacy locator schema invalid');
47
+ const expected=path.join(evidenceDir,'marker-'+locator.marker_sha256+'.terminal.json');
48
+ if(path.resolve(locator.receipt_path).toLowerCase()!==path.resolve(expected).toLowerCase())
49
+ throw new Error('R159 legacy locator receipt path mismatch');
50
+ await regular(expected);
51
+ const raw=await readFile(expected);
52
+ if(digest(raw)!==locator.receipt_sha256)
53
+ throw new Error('R159 legacy locator receipt hash mismatch');
54
+ const terminal=strictJson(raw,'located terminal');
55
+ if(terminal.schema!=='deadbyte.r153-marker-reconciliation.v1'||
56
+ terminal.state!=='MARKER_RECONCILED'||terminal.marker_sha256!==locator.marker_sha256)
57
+ throw new Error('R159 located terminal identity mismatch');
58
+ if(samePredecessor(terminal.predecessor,activePointer))
59
+ located={kind:'located',receiptPath:expected,receiptSha256:locator.receipt_sha256,
60
+ markerSha256:locator.marker_sha256};
61
+ }
62
+ const candidates=[];
63
+ for(const entry of entries){
64
+ const match=/^marker-([a-f0-9]{64})\.intent\.json$/.exec(entry.name);
65
+ if(!match)continue;
66
+ if(!entry.isFile()||entry.isSymbolicLink())throw new Error('R159 intent is not regular');
67
+ const intent=path.join(evidenceDir,entry.name);
68
+ await regular(intent);
69
+ const intentBytes=await readFile(intent),value=strictJson(intentBytes,'recovery intent');
70
+ if(value.schema!=='deadbyte.r153-marker-reconciliation-intent.v1'||
71
+ value.state!=='RECONCILIATION_CLAIMED'||value.marker_sha256!==match[1])
72
+ throw new Error('R159 intent filename/identity mismatch');
73
+ if(!samePredecessor(value.predecessor,activePointer))continue;
74
+ const terminal=path.join(evidenceDir,'marker-'+match[1]+'.terminal.json');
75
+ const terminalEntry=fileSet.get(path.basename(terminal));
76
+ let terminalExists=false;
77
+ if(terminalEntry){
78
+ if(!terminalEntry.isFile()||terminalEntry.isSymbolicLink())
79
+ throw new Error('R159 terminal is not regular');
80
+ await regular(terminal);
81
+ const parsed=strictJson(await readFile(terminal),'current terminal');
82
+ if(parsed.schema!=='deadbyte.r153-marker-reconciliation.v1'||
83
+ parsed.state!=='MARKER_RECONCILED'||parsed.marker_sha256!==match[1]||
84
+ parsed.intent_sha256!==digest(intentBytes)||
85
+ !samePredecessor(parsed.predecessor,activePointer))
86
+ throw new Error('R159 current terminal intent/subject mismatch');
87
+ terminalExists=true;
88
+ }
89
+ candidates.push({markerSha256:match[1],intent,terminal,terminalExists});
90
+ }
91
+ const pending=candidates.filter(c=>!c.terminalExists);
92
+ if(pending.length>1)throw new Error('R159 multiple matching pending reconciliation claims; manual reconciliation required');
93
+ if(pending.length===1)return {kind:'pending',pending:pending[0]};
94
+ if(located){
95
+ const other=candidates.filter(c=>c.markerSha256!==located.markerSha256);
96
+ if(other.length)throw new Error('R159 ambiguous multiple current reconciliation receipts');
97
+ return located;
98
+ }
99
+ const completed=candidates.filter(c=>c.terminalExists);
100
+ if(completed.length>1){
101
+ // Keep the original fail-closed default. A caller that independently
102
+ // verifies it is already running the exact installed release may choose
103
+ // not to reuse any fully completed receipts for a new startup.
104
+ if(allowCompletedHistory===true)
105
+ return {kind:'completed_history_ambiguous',count:completed.length};
106
+ throw new Error('R159 ambiguous multiple completed reconciliation receipts');
107
+ }
108
+ if(completed.length===1)return {kind:'completed',pending:completed[0]};
109
+ return {kind:'none'};
110
+ }
111
+
112
+ // Selection proves only that the locator and terminal bytes are internally
113
+ // consistent. Adoption is a separate boundary: reread the terminal and bind
114
+ // its R153 proof to the currently observed predecessor and policy generation.
115
+ export async function verifyLocatedLegacyMarkerReceipt({located,pointer,policySha256}){
116
+ if(located?.kind!=='located'||typeof located.receiptPath!=='string'||
117
+ !H.test(String(located.receiptSha256??''))||!H.test(String(located.markerSha256??'')))
118
+ throw new Error('R160 located receipt selection invalid');
119
+ await regular(located.receiptPath);
120
+ const raw=await readFile(located.receiptPath);
121
+ if(digest(raw)!==located.receiptSha256)
122
+ throw new Error('R160 located terminal changed after selection');
123
+ const terminal=strictJson(raw,'located terminal readback');
124
+ verifyMarkerReconciliationReceipt(terminal,{pointer,policySha256,
125
+ markerSha256:located.markerSha256});
126
+ return Object.freeze({...terminal,receipt_path:located.receiptPath,
127
+ receipt_sha256:located.receiptSha256});
128
+ }
@@ -0,0 +1,61 @@
1
+ import {createHash} from 'node:crypto';
2
+ import {lstat,readFile} from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import {parseManifest} from './release-slot.mjs';
5
+
6
+ const SEMVER=/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
7
+ const sha=bytes=>createHash('sha256').update(bytes).digest('hex');
8
+
9
+ export const R160_STARTUP_DECISION_FILES=Object.freeze([
10
+ 'package.json',
11
+ 'src/package-identity-r160.mjs',
12
+ 'src/deadbyte-cli.mjs',
13
+ 'src/legacy-marker-evidence-r159.mjs',
14
+ 'src/clean-activation-recovery-r153.mjs',
15
+ 'src/runtime-coherence.mjs',
16
+ 'src/runtime-update.mjs',
17
+ 'src/release-generation.mjs',
18
+ 'src/release-slot.mjs',
19
+ 'src/remote-authority.mjs',
20
+ 'src/foreground-session-lock.mjs',
21
+ 'src/foreground-marker-cas-win32.mjs',
22
+ 'src/windows-powershell-environment.mjs',
23
+ 'src/state-root.mjs'
24
+ ]);
25
+
26
+ async function regularUnder(root,rel){
27
+ const absolute=path.resolve(root,...rel.split('/'));
28
+ const boundary=path.relative(root,absolute);
29
+ if(boundary.startsWith(`..${path.sep}`)||path.isAbsolute(boundary))
30
+ throw new Error(`startup decision path escapes package: ${rel}`);
31
+ let cursor=path.resolve(root);
32
+ for(const part of rel.split('/')){
33
+ cursor=path.join(cursor,part);
34
+ const stat=await lstat(cursor);
35
+ if(stat.isSymbolicLink())throw new Error(`startup decision link forbidden: ${rel}`);
36
+ }
37
+ const stat=await lstat(absolute);
38
+ if(!stat.isFile())throw new Error(`startup decision file invalid: ${rel}`);
39
+ return absolute;
40
+ }
41
+
42
+ export async function verifyPackageDecisionManifest({root,expectedVersion}){
43
+ if(typeof root!=='string'||!path.isAbsolute(root))throw new Error('startup package root invalid');
44
+ if(!SEMVER.test(String(expectedVersion??'')))throw new Error('startup package version invalid');
45
+ const resolved=path.resolve(root),rootStat=await lstat(resolved);
46
+ if(!rootStat.isDirectory()||rootStat.isSymbolicLink())throw new Error('startup package root must be a real directory');
47
+ const manifestPath=await regularUnder(resolved,'MANIFEST.SHA256');
48
+ const manifestBytes=await readFile(manifestPath),manifest=parseManifest(manifestBytes.toString('utf8'));
49
+ for(const rel of R160_STARTUP_DECISION_FILES){
50
+ const expected=manifest.get(rel);
51
+ if(!expected)throw new Error(`startup decision manifest missing: ${rel}`);
52
+ const actual=sha(await readFile(await regularUnder(resolved,rel)));
53
+ if(actual!==expected)throw new Error(`startup decision file hash mismatch: ${rel}`);
54
+ }
55
+ const pkg=JSON.parse(await readFile(path.join(resolved,'package.json'),'utf8'));
56
+ if(pkg?.name!=='deadbyte-mcp'||pkg?.version!==expectedVersion)
57
+ throw new Error('startup package identity mismatch');
58
+ return Object.freeze({files:R160_STARTUP_DECISION_FILES.length,
59
+ manifest_sha256:sha(manifestBytes),version:expectedVersion});
60
+ }
61
+