deadbyte-mcp 0.15.61 → 0.15.62
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/MANIFEST.SHA256 +76 -31
- package/PROVENANCE.json +27 -8
- package/README.txt +18 -18
- package/RELEASE.json +44 -15
- package/SBOM.cdx.json +2 -2
- package/TEST-MATRIX.json +24 -2
- package/bridge/package.json +1 -1
- package/controller/deadbyte-controller.ps1 +78 -1
- package/controller/deadbyte-process-policy.json +4 -4
- package/docs/R156-LOOP-AND-EPOCH-ROUTING.md +86 -0
- package/docs/R157-ARCHITECTURE-DEPENDENCIES.md +58 -0
- package/docs/R157-ARCHITECTURE-GRAPH.json +2874 -0
- package/docs/R157-ENGINEERING-HANDOFF.md +71 -0
- package/docs/R157-FILE-DELTA.json +80 -0
- package/docs/R157-FOCUSED-VERIFICATION.log +0 -0
- package/docs/R157-REFERENCE-CAPABILITY-MATRIX.md +207 -0
- package/docs/R157-REFERENCE-DECISIONS.md +46 -0
- package/docs/R157-REFERENCE-INVENTORY.json +2849 -0
- package/docs/R158-COMBINED-REGRESSION.log +0 -0
- package/docs/R158-DELTA.json +80 -0
- package/docs/R158-EPOCH-BOOTSTRAP-GREEN.log +0 -0
- package/docs/R158-EPOCH-BOOTSTRAP-RED.log +0 -0
- package/docs/R158-EPOCH-IDENTITY-RED.log +0 -0
- package/docs/R158-FOCUSED-GREEN.log +0 -0
- package/docs/R158-PROTECTED-EPOCH-VERIFIED.md +43 -0
- package/docs/R158-PROTECTED-EXACT-SOURCE-LIVE.log +0 -0
- package/docs/R158-UPDATER-REGRESSION.log +0 -0
- package/docs/R159-COMPLETED-HISTORY-BACKUP/deadbyte-cli.mjs.before +965 -0
- package/docs/R159-COMPLETED-HISTORY-BACKUP/legacy-marker-evidence-r159.mjs.before +102 -0
- package/docs/R159-COMPLETED-HISTORY-BACKUP/r159-legacy-marker-evidence.test.mjs.before +79 -0
- package/docs/R159-COMPLETED-HISTORY-PATCH.json +26 -0
- package/docs/R159-MARKER-LOCATOR-CHECKPOINT.md +46 -0
- package/docs/R160-ENGINEERING-HANDOFF.md +32 -0
- package/docs/R160-REFERENCE-CAPABILITY-MATRIX.json +165 -0
- package/package.json +2 -2
- package/scripts/gate-windows-r160.ps1 +57 -0
- package/scripts/npm-release-r48.mjs +5 -0
- package/scripts/npm-release-readiness-r163.mjs +67 -0
- package/src/autonomous-supervisor.mjs +21 -4
- package/src/deadbyte-cli.mjs +70 -50
- package/src/legacy-marker-evidence-r159.mjs +128 -0
- package/src/package-identity-r160.mjs +61 -0
- package/src/release-generation.mjs +13 -0
- package/src/release-slot.mjs +2 -1
- package/src/remote-live-log.mjs +30 -2
- package/src/runtime-update.mjs +18 -6
- package/src/trusted-epoch-bootstrap-r158.mjs +42 -0
- package/src/version.mjs +1 -1
- package/src/windows-powershell-environment.mjs +5 -2
- package/test/r106-trusted-powershell-spawn.test.mjs +1 -0
- package/test/r130-release-security-contract.test.mjs +9 -9
- package/test/r132-release-identity.test.mjs +8 -10
- package/test/r153-prepublish-identity.test.mjs +25 -4
- package/test/r153-staging-leaf-acl.test.mjs +5 -2
- package/test/r155-commander-live-log.test.mjs +44 -0
- package/test/r155-default-live-log.test.mjs +89 -0
- package/test/r156-epoch-route-smoke.ps1 +48 -0
- package/test/r156-supervisor-pacing.test.mjs +59 -0
- package/test/r157-architecture-map.mjs +61 -0
- package/test/r157-reference-survey.mjs +91 -0
- package/test/r157-supervisor-singleflight.test.mjs +32 -0
- package/test/r158-epoch-bootstrap.test.mjs +34 -0
- package/test/r159-legacy-marker-evidence.test.mjs +100 -0
- package/test/r159-legacy-marker-evidence.test.mjs.before-assertion-fix-20261025-120246.bak +100 -0
- package/test/r160-marker-proof.test.mjs +87 -0
- package/test/r160-package-decision-manifest.test.mjs +67 -0
- package/test/r160-release-contract.test.mjs +30 -0
- package/test/r160-stable-launcher.test.mjs +10 -0
- package/test/r163-isolated-slot-rollback-restore.test.mjs +58 -0
- package/test/r163-publish-fence.test.mjs +13 -0
- package/test/r48-release-contract.test.mjs +7 -1
- package/test/r58-launcher-shell.test.mjs +4 -1
- package/test/r59-tool-surface-compat.test.mjs +1 -1
- package/test/release-generation.test.mjs +16 -0
- package/test/remote-live-log-r37.test.mjs +5 -3
- package/test/remote-session-cli.test.mjs +11 -11
- package/test/stabilization-state-root.test.mjs +2 -1
- 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
|
+
}
|
|
@@ -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
|
|
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
|
|
123
|
+
async scanNow() { return scanOnce(); },
|
|
107
124
|
async stop() { abort.abort(); await done; }
|
|
108
125
|
});
|
|
109
126
|
}
|
package/src/deadbyte-cli.mjs
CHANGED
|
@@ -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]
|
|
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(
|
|
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,79 @@ 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
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
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
|
+
const selected=await selectLegacyMarkerEvidence({evidenceDir,activePointer,
|
|
551
|
+
allowCompletedHistory:historyAllowed});
|
|
552
|
+
if(selected.kind==='none')return null;
|
|
553
|
+
if(selected.kind==='completed_history_ambiguous'){
|
|
554
|
+
if(!historyAllowed)throw new Error('R159 completed history cannot be reused across a release change');
|
|
555
|
+
// Nothing is pending: do not select an arbitrary completed receipt.
|
|
556
|
+
// A future upgrade still requires its own exact signed migration proof.
|
|
557
|
+
return null;
|
|
558
|
+
}
|
|
559
|
+
if(selected.kind==='located'){
|
|
560
|
+
const readback=await readActiveRuntimeBinding(ctx.root);
|
|
561
|
+
for(const key of ['release_id','version','manifest_sha256','generation_manifest_sha256'])
|
|
562
|
+
if(readback?.[key]!==activePointer?.[key])
|
|
563
|
+
throw new Error('R159 active release changed during located marker verification');
|
|
564
|
+
const {policySha256}=await readMarkerRecoveryAuthority(ctx,readback);
|
|
565
|
+
const verified=await verifyLocatedLegacyMarkerReceipt({located:selected,pointer:readback,policySha256});
|
|
566
|
+
const finalPointer=await readActiveRuntimeBinding(ctx.root);
|
|
567
|
+
for(const key of ['release_id','version','manifest_sha256','generation_manifest_sha256'])
|
|
568
|
+
if(finalPointer?.[key]!==readback?.[key])
|
|
569
|
+
throw new Error('R160 active release changed after located marker verification');
|
|
570
|
+
return verified;
|
|
571
|
+
}
|
|
572
|
+
// Completed historical receipts are not a pending migration for an
|
|
573
|
+
// already-online current release. Keep bytes and their SHA untouched.
|
|
574
|
+
if(selected.kind==='completed'&&observedAuthority(ctx).state==='ONLINE')return null;
|
|
575
|
+
if(selected.kind==='pending'&&observedAuthority(ctx).state!=='OFFLINE')
|
|
576
|
+
throw new Error('R159 pending marker reconciliation requires an offline runtime');
|
|
533
577
|
// Crash recovery for the narrow window after marker CAS deletion and
|
|
534
578
|
// before terminal receipt publication. Never infer completion from the
|
|
535
579
|
// missing marker alone: require exactly one durable intent and re-observe
|
|
536
580
|
// the full predecessor/policy/grant/runtime subject.
|
|
537
|
-
|
|
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);
|
|
581
|
+
const pending=selected.pending,intentInfo=await lstat(selected.pending.intent);
|
|
553
582
|
if(!intentInfo.isFile()||intentInfo.isSymbolicLink())throw new Error('marker reconciliation intent is not a regular file');
|
|
554
583
|
const intentBytes=await readFile(pending.intent),intent=JSON.parse(intentBytes.toString('utf8').replace(/^\uFEFF/,''));
|
|
555
584
|
if(intent.marker_sha256!==pending.markerSha256)throw new Error('marker reconciliation intent filename identity mismatch');
|
|
556
585
|
const active=await readActiveRuntimeBinding(ctx.root);
|
|
557
586
|
if(!active)throw new Error('marker reconciliation predecessor pointer missing');
|
|
587
|
+
for(const key of ['release_id','version','manifest_sha256','generation_manifest_sha256'])
|
|
588
|
+
if(active[key]!==activePointer?.[key])
|
|
589
|
+
throw new Error('R159 active release changed during marker recovery');
|
|
558
590
|
const observed=observedAuthority(ctx);
|
|
559
|
-
const
|
|
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
|
-
}
|
|
591
|
+
const {policySha256,grantPresence}=await readMarkerRecoveryAuthority(ctx,active);
|
|
574
592
|
if(await readRemoteMarkerSnapshot(remoteSessionMarker(ctx.root))!==null)
|
|
575
593
|
throw new Error('marker reconciliation marker reappeared');
|
|
576
594
|
const intentSha256=createHash('sha256').update(intentBytes).digest('hex');
|
|
@@ -589,7 +607,7 @@ async function recoverRemoteSession(ctx,{displayMode='unicode'}={}){
|
|
|
589
607
|
}
|
|
590
608
|
const locator={schema:'deadbyte.r153-pending-migration.v1',receipt_path:pending.terminal,
|
|
591
609
|
receipt_sha256:reconciled.receipt_sha256,marker_sha256:reconciled.marker_sha256};
|
|
592
|
-
await publishExclusiveJson(
|
|
610
|
+
await publishExclusiveJson(path.join(evidenceDir,'marker-'+reconciled.marker_sha256+'.locator.json'),locator);
|
|
593
611
|
writeRemote(process.stdout,`${remoteTag('recovery',displayMode)} Interrupted marker reconciliation completed (${reconciled.marker_sha256.slice(0,12)}).\n`,displayMode);
|
|
594
612
|
return {...reconciled,receipt_path:pending.terminal};
|
|
595
613
|
}
|
|
@@ -692,7 +710,7 @@ async function recoverRemoteSession(ctx,{displayMode='unicode'}={}){
|
|
|
692
710
|
deleteIfHash:async expected=>deletePinnedRemoteMarker(markerPath,expected)});
|
|
693
711
|
const locator={schema:'deadbyte.r153-pending-migration.v1',receipt_path:terminalPath,
|
|
694
712
|
receipt_sha256:reconciled.receipt_sha256,marker_sha256:reconciled.marker_sha256};
|
|
695
|
-
await publishExclusiveJson(
|
|
713
|
+
await publishExclusiveJson(path.join(evidenceDir,'marker-'+reconciled.marker_sha256+'.locator.json'),locator);
|
|
696
714
|
writeRemote(process.stdout,`${remoteTag('recovery',displayMode)} Stale session evidence reconciled (${reconciled.marker_sha256.slice(0,12)}).\n`,displayMode);
|
|
697
715
|
return {...reconciled,receipt_path:terminalPath};
|
|
698
716
|
}
|
|
@@ -754,6 +772,8 @@ async function runRemoteSession({resumeCursor=null,verbose=false,trace=false,fil
|
|
|
754
772
|
const startupLock=await acquireForegroundSessionLock({root:ctx.root,powershell:ctx.powershell});
|
|
755
773
|
let marker;
|
|
756
774
|
try{
|
|
775
|
+
startupLock.assertHeld();
|
|
776
|
+
await verifyPackageDecisionManifest({root:CURRENT_PACKAGE_ROOT,expectedVersion:DEADBYTE_VERSION});
|
|
757
777
|
startupLock.assertHeld();
|
|
758
778
|
const migrationContext=await recoverRemoteSession(ctx,{displayMode});
|
|
759
779
|
startupLock.assertHeld();
|
|
@@ -766,7 +786,7 @@ async function runRemoteSession({resumeCursor=null,verbose=false,trace=false,fil
|
|
|
766
786
|
filePath:remoteResultJournalPath(ctx.root,marker.result_session_id),sessionId:marker.result_session_id,maxEntries:2048
|
|
767
787
|
});
|
|
768
788
|
const rootPaths=await readLiveRootPaths(ctx.root);
|
|
769
|
-
const activityTracker=createLiveTimelineTracker({verbose,displayMode,rootPaths,trace
|
|
789
|
+
const activityTracker=createLiveTimelineTracker({verbose,displayMode,rootPaths,trace,...defaultForegroundTimelineOptions({verbose})});
|
|
770
790
|
const timelineFilter=filterExpression?createLiveTimelineFilter(filterExpression):null;
|
|
771
791
|
const timelineWriter=createLiveTimelineWriter({stream:process.stdout,displayMode,rewrite:false});
|
|
772
792
|
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
|
+
|
|
@@ -185,6 +185,10 @@ function renderControllerConfig({currentConfig,coreRoot,workspaceRoot,processPol
|
|
|
185
185
|
const supervisor=currentConfig.Supervisor??{},bridge=currentConfig.Bridge??{},cloud=currentConfig.Cloud??{};
|
|
186
186
|
return [
|
|
187
187
|
'@{'," Schema = 'deadbyte.controller.v1'",` CoreRoot = ${psQuote(coreRoot)}`,
|
|
188
|
+
// A pre-provisioned, OS-owned exact release copy; never auto-discover
|
|
189
|
+
// an unverified directory or grant authority while creating a generation.
|
|
190
|
+
...(typeof currentConfig.AuthorityEpochRoot==='string'&¤tConfig.AuthorityEpochRoot.length>0
|
|
191
|
+
?[` AuthorityEpochRoot = ${psQuote(currentConfig.AuthorityEpochRoot)}`]:[]),
|
|
188
192
|
` WorkspaceRoot = ${psQuote(workspaceRoot)}`,
|
|
189
193
|
` MachineRuntimeEnabled = ${psBool(currentConfig.MachineRuntimeEnabled!==false)}`,
|
|
190
194
|
` AgentRuntimeEnabled = ${psBool(currentConfig.AgentRuntimeEnabled)}`,
|
|
@@ -270,6 +274,13 @@ export async function createGenerationBundle({stateRoot,releaseRoot,developmentR
|
|
|
270
274
|
}
|
|
271
275
|
if(typeof currentConfig?.Bridge?.ScriptPath!=='string'||!path.isAbsolute(currentConfig.Bridge.ScriptPath)) throw new Error('generation Bridge ScriptPath must be absolute');
|
|
272
276
|
if(typeof currentConfig?.Bridge?.ScriptSha256!=='string'||!HEX64.test(currentConfig.Bridge.ScriptSha256)) throw new Error('generation Bridge ScriptSha256 invalid');
|
|
277
|
+
// Legacy generations omit Epoch binding. Present values must be explicit
|
|
278
|
+
// Windows drive-absolute paths, never an implicit writable source.
|
|
279
|
+
const epochRoot=currentConfig.AuthorityEpochRoot;
|
|
280
|
+
if(epochRoot!==undefined&&(typeof epochRoot!=='string'||!/^[A-Za-z]:[\\/]/.test(epochRoot)||
|
|
281
|
+
!path.win32.isAbsolute(epochRoot)||epochRoot.includes('\0')||
|
|
282
|
+
path.win32.parse(epochRoot).root.toLowerCase()===path.win32.normalize(epochRoot).toLowerCase()))
|
|
283
|
+
throw new Error('generation AuthorityEpochRoot must be a Windows drive-absolute directory');
|
|
273
284
|
const semanticSource=String(currentConfig.SemanticLspConfigFile??'');
|
|
274
285
|
const semanticExpected=String(currentConfig.SemanticLspConfigSha256??'').toLowerCase();
|
|
275
286
|
if(Boolean(semanticSource)!==Boolean(semanticExpected)) throw new Error('generation semantic LSP config path/SHA must be supplied together');
|
|
@@ -316,6 +327,8 @@ export async function createGenerationBundle({stateRoot,releaseRoot,developmentR
|
|
|
316
327
|
mcp_url_file:currentConfig.McpUrlFile??'',mcp_url_file_sha256:currentConfig.McpUrlFileSha256??'',mcp_path_file:currentConfig.McpPathFile??'',
|
|
317
328
|
start_timeout_seconds:Number(currentConfig.StartTimeoutSeconds??30),probe_timeout_seconds:Number(currentConfig.ProbeTimeoutSeconds??3),
|
|
318
329
|
semantic_lsp_config_sha256:semanticConfigSha256,
|
|
330
|
+
// Changing a protected code origin changes this immutable generation.
|
|
331
|
+
...(epochRoot!==undefined?{authority_epoch_root:epochRoot}:{}),
|
|
319
332
|
process_policy_sha256:processBytes?sha256(processBytes):null,
|
|
320
333
|
desktop_policy_sha256:desktopBytes?sha256(desktopBytes):null,
|
|
321
334
|
coding_policy_sha256:sha256(codingBytes),autonomous_policy_sha256:sha256(autonomousBytes),host_policy_sha256:hostBytes?sha256(hostBytes):null
|
package/src/release-slot.mjs
CHANGED
|
@@ -205,8 +205,9 @@ export function renderStableLauncherCmd(){
|
|
|
205
205
|
return [
|
|
206
206
|
'@echo off',
|
|
207
207
|
'setlocal EnableExtensions DisableDelayedExpansion',
|
|
208
|
+
'if not defined SystemRoot exit /b 1',
|
|
208
209
|
'if not defined DEADBYTE_STATE_ROOT set "DEADBYTE_STATE_ROOT=%USERPROFILE%\\.deadbyte-mcp"',
|
|
209
|
-
'powershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass -File "%DEADBYTE_STATE_ROOT%\\bin\\DEADBYTE-MCP.ps1" %*',
|
|
210
|
+
'"%SystemRoot%\\System32\\WindowsPowerShell\\v1.0\\powershell.exe" -NoLogo -NoProfile -ExecutionPolicy Bypass -File "%DEADBYTE_STATE_ROOT%\\bin\\DEADBYTE-MCP.ps1" %*',
|
|
210
211
|
'exit /b %ERRORLEVEL%',
|
|
211
212
|
''
|
|
212
213
|
].join('\r\n');
|