deadbyte-mcp 0.15.37 → 0.15.39
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 +41 -22
- package/PROVENANCE.json +55 -20
- package/README.txt +19 -19
- package/RELEASE.json +50 -21
- package/SBOM.cdx.json +2 -2
- package/TEST-MATRIX.json +67 -15
- package/bridge/package.json +1 -1
- package/bridge/src/bridge.mjs +4 -4
- package/bridge/src/r40-governor.mjs +109 -19
- package/controller/deadbyte-process-policy.json +4 -4
- package/docs/R61-TASK-CONTRACT.md +11 -0
- package/docs/r61-reference-evidence.md +18 -0
- package/package.json +5 -3
- package/scripts/gate-windows-r61-prepublish.ps1 +45 -0
- package/scripts/gate-windows-r62-prepublish.ps1 +45 -0
- package/scripts/npm-artifact-identity-r61.mjs +22 -0
- package/scripts/npm-release-r48.mjs +21 -5
- package/scripts/r61-mixed-lane-benchmark.mjs +98 -0
- package/scripts/r62-focused.ps1 +9 -0
- package/scripts/r62-full.ps1 +9 -0
- package/scripts/r62-live-transaction-audit.mjs +17 -0
- package/scripts/r62-unresolved-transaction-inspect.mjs +54 -0
- package/src/deadbyte-cli.mjs +9 -6
- package/src/doctor-transactions-r58.mjs +79 -8
- package/src/remote-live-log.mjs +28 -5
- package/src/runtime-update.mjs +28 -4
- package/src/version.mjs +1 -1
- package/test/bridge-runtime-r59.test.mjs +5 -5
- package/test/commander-schema-differential-r48.test.mjs +1 -1
- package/test/r58-doctor-transactions.test.mjs +2 -2
- package/test/r60-release-contract.test.mjs +4 -3
- package/test/r61-control-lane.test.mjs +154 -0
- package/test/r61-live-timeline-bounds.test.mjs +31 -0
- package/test/r61-npm-artifact-identity.test.mjs +17 -0
- package/test/r61-release-bridge-binding.test.mjs +32 -0
- package/test/r61-release-contract.test.mjs +45 -0
- package/test/r62-doctor-receipt-semantics.test.mjs +50 -0
- package/test/r62-doctor-transactions-reconcile.test.mjs +208 -0
- package/test/r62-release-contract.test.mjs +59 -0
- package/test/r62-rollback-failure-receipt.test.mjs +19 -0
- package/test/remote-live-log-r37.test.mjs +23 -6
- package/test/remote-session-cli.test.mjs +1 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { verifyPublishedTarballIdentity } from './npm-artifact-identity-r61.mjs';
|
|
2
3
|
import { mkdtemp, readFile, rm, unlink } from 'node:fs/promises';
|
|
3
4
|
import path from 'node:path';
|
|
4
5
|
import os from 'node:os';
|
|
@@ -58,6 +59,8 @@ const allowedUnmanifested = new Set([
|
|
|
58
59
|
|
|
59
60
|
async function main() {
|
|
60
61
|
const isVerifyOnly = process.argv.includes('--verify-only') || process.argv.includes('--dry-run');
|
|
62
|
+
const publishOnly = process.argv.includes('--publish-only');
|
|
63
|
+
if (isVerifyOnly && publishOnly) throw new Error('--publish-only conflicts with verify-only mode');
|
|
61
64
|
|
|
62
65
|
console.log(`[1/7] Reading package.json and checking registry publication status... ${isVerifyOnly ? '(MODE: VERIFY-ONLY / DRY-RUN)' : '(MODE: PUBLISH)'}`);
|
|
63
66
|
const pkg = JSON.parse(await readFile(path.join(root, 'package.json'), 'utf8'));
|
|
@@ -180,6 +183,8 @@ async function main() {
|
|
|
180
183
|
return;
|
|
181
184
|
}
|
|
182
185
|
|
|
186
|
+
const localBytes = await readFile(tarball);
|
|
187
|
+
|
|
183
188
|
console.log('[6/7] Publishing artifact to npm registry...');
|
|
184
189
|
publishAttempted = true;
|
|
185
190
|
let publishOut;
|
|
@@ -193,8 +198,15 @@ async function main() {
|
|
|
193
198
|
}
|
|
194
199
|
throw publishError;
|
|
195
200
|
}
|
|
196
|
-
const
|
|
201
|
+
const viewed = parseJson(runNpm(['view', `deadbyte-mcp@${version}`, 'version', 'dist.integrity', 'dist.shasum', 'dist.tarball', '--json'], { timeout: 120000 }));
|
|
202
|
+
const published = Array.isArray(viewed) ? viewed[0] : viewed;
|
|
203
|
+
const dist = published?.dist ?? published;
|
|
204
|
+
if(typeof dist?.tarball !== 'string')throw new Error('published registry tarball URL missing');
|
|
205
|
+
const registryResponse = await fetch(dist.tarball,{signal:AbortSignal.timeout(120000)});
|
|
206
|
+
if(!registryResponse.ok) throw new Error(`registry tarball fetch failed status=${registryResponse.status}`);
|
|
207
|
+
const registryBytes = Buffer.from(await registryResponse.arrayBuffer());
|
|
197
208
|
const tags = parseJson(runNpm(['view', 'deadbyte-mcp', 'dist-tags', '--json'], { timeout: 120000 }));
|
|
209
|
+
const identity=verifyPublishedTarballIdentity({version,localBytes,registryBytes,published,tags});
|
|
198
210
|
|
|
199
211
|
console.log('[7/7] Post-publish external registry smoke test (executed outside repo in temp)...');
|
|
200
212
|
const tempDir = os.tmpdir();
|
|
@@ -211,16 +223,20 @@ async function main() {
|
|
|
211
223
|
timeout: 300000,
|
|
212
224
|
});
|
|
213
225
|
|
|
214
|
-
|
|
215
|
-
|
|
226
|
+
if(!publishOnly){
|
|
227
|
+
console.log('[8/8] Activating the published release into the live immutable slot and waiting for verified handoff...');
|
|
228
|
+
run(node, [path.join(root, 'scripts', 'deploy-r48.mjs')], { cwd: root, timeout: 900000 });
|
|
229
|
+
}else console.log('[8/8] Publication verified; activation deliberately deferred to the separate production gate.');
|
|
216
230
|
|
|
217
231
|
releaseSucceeded = true;
|
|
218
232
|
console.log('\nRELEASE SUCCESSFUL:');
|
|
219
233
|
console.log(JSON.stringify({
|
|
220
|
-
status: 'published',
|
|
234
|
+
status: publishOnly?'published_verified_activation_pending':'published',
|
|
221
235
|
name: pkg.name,
|
|
222
236
|
version,
|
|
223
237
|
tarball: filename,
|
|
238
|
+
tarball_sha256: identity.tarball_sha256,
|
|
239
|
+
npm_integrity: identity.npm_integrity,
|
|
224
240
|
published,
|
|
225
241
|
published_output_tail: publishOut.slice(-2000),
|
|
226
242
|
dist_tags: tags,
|
|
@@ -231,7 +247,7 @@ async function main() {
|
|
|
231
247
|
}, null, 2));
|
|
232
248
|
|
|
233
249
|
} finally {
|
|
234
|
-
if (releaseSucceeded || !publishAttempted) {
|
|
250
|
+
if ((releaseSucceeded && !publishOnly) || !publishAttempted) {
|
|
235
251
|
await unlink(tarball).catch(() => {});
|
|
236
252
|
} else {
|
|
237
253
|
console.error('Release artifact preserved after failure: ' + tarball);
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import {mkdtemp,mkdir,readFile,readdir,stat,writeFile,open,rm} from 'node:fs/promises';
|
|
2
|
+
import {spawn} from 'node:child_process';
|
|
3
|
+
import {createHash} from 'node:crypto';
|
|
4
|
+
import {monitorEventLoopDelay,performance} from 'node:perf_hooks';
|
|
5
|
+
import {pathToFileURL} from 'node:url';
|
|
6
|
+
import os from 'node:os';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
|
|
9
|
+
const roots=process.argv.slice(2);
|
|
10
|
+
if(roots.length!==2||roots.some(item=>!path.isAbsolute(item)))throw new Error('two absolute governor module paths required');
|
|
11
|
+
const levels=(process.env.DEADBYTE_R61_BENCH_LEVELS??'1,2,4,8,16,32').split(',').map(Number);
|
|
12
|
+
if(!levels.length||levels.some(level=>![1,2,4,8,16,32].includes(level))||new Set(levels).size!==levels.length)throw new Error('benchmark levels invalid');
|
|
13
|
+
const ROUNDS=Number(process.env.DEADBYTE_R61_BENCH_ROUNDS??3),OPS=160;
|
|
14
|
+
if(!Number.isInteger(ROUNDS)||ROUNDS<3||ROUNDS>15)throw new Error('benchmark rounds must be 3..15');
|
|
15
|
+
const scratchRoot=process.env.DEADBYTE_R61_SCRATCH_ROOT;
|
|
16
|
+
if(!scratchRoot||!path.isAbsolute(scratchRoot))throw new Error('explicit absolute DEADBYTE_R61_SCRATCH_ROOT required');
|
|
17
|
+
const quant=(values,p)=>{const ordered=[...values].sort((a,b)=>a-b);return ordered[Math.max(0,Math.ceil(ordered.length*p)-1)]};
|
|
18
|
+
const sha=value=>createHash('sha256').update(value).digest('hex');
|
|
19
|
+
|
|
20
|
+
async function childOutput(){
|
|
21
|
+
const child=spawn(process.execPath,['-e','process.stdout.write("R61_CHILD_OK")'],{windowsHide:true,stdio:['ignore','pipe','pipe']});
|
|
22
|
+
let output='';for await(const chunk of child.stdout)output+=chunk;
|
|
23
|
+
const exit=await new Promise(resolve=>child.once('close',resolve));
|
|
24
|
+
if(exit!==0||output!=='R61_CHILD_OK')throw new Error('bounded child process oracle failed');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function runCase(modulePath,level,round){
|
|
28
|
+
const {createBridgeGovernor}=await import(pathToFileURL(modulePath).href);
|
|
29
|
+
await mkdir(scratchRoot,{recursive:true});
|
|
30
|
+
const scratch=await mkdtemp(path.join(scratchRoot,'deadbyte-r61-bench-'));
|
|
31
|
+
const fixture=path.join(scratch,'fixture'),audit=path.join(scratch,'presentation.jsonl');
|
|
32
|
+
await mkdir(fixture);
|
|
33
|
+
const sample='deadbyte bounded corpus สวัสดี\n'.repeat(512);
|
|
34
|
+
for(let i=0;i<16;i++)await writeFile(path.join(fixture,`f${i}.txt`),sample);
|
|
35
|
+
const governor=createBridgeGovernor();
|
|
36
|
+
const calls=[],control=[],counts={error:0,unknown:0},peak={rss:process.memoryUsage().rss,queue:0,active:0};
|
|
37
|
+
const delay=monitorEventLoopDelay({resolution:20});delay.enable();
|
|
38
|
+
let next=0;
|
|
39
|
+
const started=performance.now();
|
|
40
|
+
async function worker(){
|
|
41
|
+
while(next<OPS){
|
|
42
|
+
const i=next++;
|
|
43
|
+
const kind=i%16;
|
|
44
|
+
const tool=kind<3?(kind===0?'machine_ping':'machine_status'):
|
|
45
|
+
kind<5?'capability_manifest':kind<8?'machine_fs_read':kind<10?'coding_search':
|
|
46
|
+
kind<12?'machine_fs_write':kind===12?'process_session_start':kind<15?'coding_file_read':'machine_process_list';
|
|
47
|
+
const t=performance.now();let ticket;
|
|
48
|
+
try{
|
|
49
|
+
ticket=await governor.acquire(tool);
|
|
50
|
+
if(tool==='machine_ping')sha(`nonce-${round}-${i}`);
|
|
51
|
+
else if(tool==='machine_status')await stat(fixture);
|
|
52
|
+
else if(tool==='capability_manifest')sha(await readFile(roots[0]));
|
|
53
|
+
else if(tool==='coding_search'){
|
|
54
|
+
const names=await readdir(fixture);
|
|
55
|
+
for(const name of names)if((await readFile(path.join(fixture,name),'utf8')).includes('bounded'))break;
|
|
56
|
+
}else if(tool==='machine_fs_write')await writeFile(path.join(scratch,`out-${i}.txt`),sample.slice(0,128));
|
|
57
|
+
else if(tool==='process_session_start')await childOutput();
|
|
58
|
+
else if(tool==='machine_process_list')os.cpus().length;
|
|
59
|
+
else await readFile(path.join(fixture,`f${i%16}.txt`));
|
|
60
|
+
const line=JSON.stringify({kind,at:i,hash:sha(String(i))})+'\n';
|
|
61
|
+
const handle=await open(audit,'a');try{await handle.writeFile(line);await handle.sync();}finally{await handle.close();}
|
|
62
|
+
}catch(error){counts.error++;throw error;}
|
|
63
|
+
finally{
|
|
64
|
+
ticket?.release();
|
|
65
|
+
const elapsed=performance.now()-t;
|
|
66
|
+
calls.push(elapsed);if(kind<3)control.push(elapsed);
|
|
67
|
+
const state=governor.snapshot();
|
|
68
|
+
peak.rss=Math.max(peak.rss,process.memoryUsage().rss);
|
|
69
|
+
peak.queue=Math.max(peak.queue,...Object.values(state).map(value=>value.queued));
|
|
70
|
+
peak.active=Math.max(peak.active,...Object.values(state).map(value=>value.active));
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
try{
|
|
75
|
+
await Promise.all(Array.from({length:level},()=>worker()));
|
|
76
|
+
await governor.close();
|
|
77
|
+
const total=performance.now()-started;
|
|
78
|
+
return {level,round,n:calls.length,throughput_ops_s:OPS*1000/total,p50_ms:quant(calls,.5),p95_ms:quant(calls,.95),
|
|
79
|
+
p99_ms:quant(calls,.99),max_ms:Math.max(...calls),control_p95_ms:quant(control,.95),
|
|
80
|
+
event_loop_p95_ms:delay.percentile(95)/1e6,peak_rss_bytes:peak.rss,peak_queue:peak.queue,
|
|
81
|
+
peak_active:peak.active,errors:counts.error,unknown:counts.unknown};
|
|
82
|
+
}finally{delay.disable();await rm(scratch,{recursive:true,force:true});}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const results=[];
|
|
86
|
+
for(const level of levels)for(let round=0;round<ROUNDS;round++){
|
|
87
|
+
const order=round%2===0?[0,1]:[1,0];
|
|
88
|
+
for(const index of order)results.push({target:index,...await runCase(roots[index],level,round)});
|
|
89
|
+
}
|
|
90
|
+
const report={schema:'deadbyte.r61.mixed-lane-benchmark.v1',node:process.version,platform:process.platform,
|
|
91
|
+
governor_sha256:await Promise.all(roots.map(async value=>sha(await readFile(value)))),levels,rounds:ROUNDS,operations_per_run:OPS,
|
|
92
|
+
limitations:['direct governor and real scratch I/O, not full MCP transport','presentation traffic is fsynced fixture data, not signed audit'],results};
|
|
93
|
+
const outputPath=process.env.DEADBYTE_R61_BENCH_OUTPUT;
|
|
94
|
+
if(outputPath){
|
|
95
|
+
if(!path.isAbsolute(outputPath))throw new Error('benchmark output must be absolute');
|
|
96
|
+
await writeFile(outputPath,JSON.stringify(report,null,2)+'\n',{flag:'wx'});
|
|
97
|
+
console.log(JSON.stringify({schema:report.schema,output_path:outputPath,result_count:results.length,governor_sha256:report.governor_sha256}));
|
|
98
|
+
}else console.log(JSON.stringify(report,null,2));
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import {inspectDoctorTransactions} from '../src/doctor-transactions-r58.mjs';
|
|
2
|
+
|
|
3
|
+
const result=await inspectDoctorTransactions();
|
|
4
|
+
const summary=result.findings.find(item=>item.evidence?.receipt_verification_performed===true)?.evidence??null;
|
|
5
|
+
const unresolved=result.findings
|
|
6
|
+
.filter(item=>item.level==='WARN'&&item.evidence?.recorded_phase)
|
|
7
|
+
.map(item=>({schema:item.evidence.schema,phase:item.evidence.recorded_phase,state_sha256:item.evidence.state_sha256}));
|
|
8
|
+
const phases={};
|
|
9
|
+
for(const item of unresolved) phases[item.phase]=(phases[item.phase]??0)+1;
|
|
10
|
+
console.log(JSON.stringify({
|
|
11
|
+
status:result.status,
|
|
12
|
+
summary,
|
|
13
|
+
unresolved_count:unresolved.length,
|
|
14
|
+
unresolved_phases:phases,
|
|
15
|
+
fail_count:result.findings.filter(item=>item.level==='FAIL').length,
|
|
16
|
+
inventory_warning:result.findings.some(item=>item.evidence?.inventory_complete===false)
|
|
17
|
+
},null,2));
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import {opendir,lstat,readFile} from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import {createHash} from 'node:crypto';
|
|
5
|
+
|
|
6
|
+
const state=path.resolve(process.env.DEADBYTE_STATE_ROOT||path.join(os.homedir(),'.deadbyte-mcp'));
|
|
7
|
+
const root=path.join(state,'evidence','updates');
|
|
8
|
+
const sha=b=>createHash('sha256').update(b).digest('hex');
|
|
9
|
+
const terminal=new Set(['completed','superseded','failed_receipted']);
|
|
10
|
+
const rows=[];
|
|
11
|
+
for await(const entry of await opendir(root)){
|
|
12
|
+
if(!entry.isDirectory()||entry.isSymbolicLink()) continue;
|
|
13
|
+
const dir=path.join(root,entry.name),stateFile=path.join(dir,'state.json');
|
|
14
|
+
let stateBytes,body;
|
|
15
|
+
try{
|
|
16
|
+
const info=await lstat(stateFile);
|
|
17
|
+
if(!info.isFile()||info.isSymbolicLink()||info.size>1048576) continue;
|
|
18
|
+
stateBytes=await readFile(stateFile);
|
|
19
|
+
body=JSON.parse(stateBytes.toString('utf8').replace(/^\uFEFF/,''));
|
|
20
|
+
}catch{continue;}
|
|
21
|
+
if(!['deadbyte.r27-slot-update-state.v1','deadbyte.r31-slot-rollback-state.v1'].includes(body?.schema)) continue;
|
|
22
|
+
const has=async name=>{try{const st=await lstat(path.join(dir,name));return st.isFile()&&!st.isSymbolicLink();}catch{return false;}};
|
|
23
|
+
if(terminal.has(body.phase)||await has('receipt.json')||await has('failure-receipt.json')) continue;
|
|
24
|
+
const handoff=await has('handoff.json');
|
|
25
|
+
let handoffSummary=null;
|
|
26
|
+
if(handoff){
|
|
27
|
+
try{
|
|
28
|
+
const h=JSON.parse((await readFile(path.join(dir,'handoff.json'),'utf8')).replace(/^\uFEFF/,''));
|
|
29
|
+
handoffSummary={schema:h?.body?.schema??h?.schema??null,body:h?.body??h};
|
|
30
|
+
}catch(error){handoffSummary={parse_error:String(error?.message??error)};}
|
|
31
|
+
}
|
|
32
|
+
rows.push({
|
|
33
|
+
entry:entry.name,
|
|
34
|
+
entry_sha256:sha(Buffer.from(entry.name,'utf8')),
|
|
35
|
+
state_sha256:sha(stateBytes),
|
|
36
|
+
schema:body.schema,
|
|
37
|
+
phase:body.phase,
|
|
38
|
+
update_id:body.update_id??null,
|
|
39
|
+
rollback_id:body.rollback_id??null,
|
|
40
|
+
prior_pointer:body.prior_pointer??null,
|
|
41
|
+
new_pointer:body.new_pointer??null,
|
|
42
|
+
active_pointer:body.active_pointer??null,
|
|
43
|
+
old_runtime:body.old_runtime??null,
|
|
44
|
+
new_runtime:body.new_runtime??null,
|
|
45
|
+
error:body.error??null,
|
|
46
|
+
recovery:body.recovery??null,
|
|
47
|
+
has_handoff:handoff,
|
|
48
|
+
handoff:handoffSummary
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
rows.sort((a,b)=>a.entry.localeCompare(b.entry));
|
|
52
|
+
let active=null;
|
|
53
|
+
try{active=JSON.parse(await readFile(path.join(state,'active-release.json'),'utf8'));}catch{}
|
|
54
|
+
console.log(JSON.stringify({state_root:state,active_pointer:active,unresolved_count:rows.length,rows},null,2));
|
package/src/deadbyte-cli.mjs
CHANGED
|
@@ -226,12 +226,12 @@ function parseAuditLogLine(file,line){
|
|
|
226
226
|
if(!/^segment-\d{8}\.jsonl$/.test(path.basename(file))||!line) return null;
|
|
227
227
|
try{return JSON.parse(line);}catch{return null;}
|
|
228
228
|
}
|
|
229
|
-
function formatLogLine(file,line,{auditEvent=null,activityTracker=null,sequence=null,verbose=false,displayMode='unicode'}={}){
|
|
229
|
+
function formatLogLine(file,line,{auditEvent=null,activityTracker=null,sequence=null,sourceSequence=null,verbose=false,displayMode='unicode'}={}){
|
|
230
230
|
if(!line) return null;
|
|
231
231
|
if(/^segment-\d{8}\.jsonl$/.test(path.basename(file))){
|
|
232
232
|
const event=auditEvent??parseAuditLogLine(file,line);
|
|
233
233
|
if(!event) return null;
|
|
234
|
-
return activityTracker?activityTracker.observe(event,{sequence}):formatCompactAuditEvent(event);
|
|
234
|
+
return activityTracker?activityTracker.observe(event,{sequence,sourceSequence}):formatCompactAuditEvent(event);
|
|
235
235
|
}
|
|
236
236
|
return formatRuntimeDiagnosticLine(file,line,{verbose,displayMode});
|
|
237
237
|
}
|
|
@@ -328,7 +328,7 @@ function timelineFilterEvent(timeline){
|
|
|
328
328
|
async function tailRemoteLogs(root,signal,{journal=null,offsets=null,activityTracker=null,timelineFilter=null,timelineWriter=null,verbose=false,displayMode='unicode'}={}){
|
|
329
329
|
process.stdout.write(verbose
|
|
330
330
|
? '[LOG] Verbose live timeline. Full sanitized request detail enabled; Ctrl+C disconnects and revokes authority.\n'
|
|
331
|
-
: '[LOG] Human live timeline. Transport IN/OUT hidden; full replay/audit detail remains on disk. Ctrl+C disconnects and revokes authority.\n');
|
|
331
|
+
: '[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');
|
|
332
332
|
const activeOffsets=offsets??await snapshotRemoteLogOffsets(root);
|
|
333
333
|
const partialLines=new Map();
|
|
334
334
|
let liveFiles=[...activeOffsets.keys()];
|
|
@@ -358,7 +358,8 @@ async function tailRemoteLogs(root,signal,{journal=null,offsets=null,activityTra
|
|
|
358
358
|
: await journal.recordTerminal(timeline);
|
|
359
359
|
if(recorded?.duplicate) continue;
|
|
360
360
|
const accepted=timelineFilter&&timeline?timelineFilter.accept(timelineFilterEvent(timeline)):true;
|
|
361
|
-
const formatted=formatLogLine(file,line,{auditEvent,activityTracker,sequence:recorded?.event?.seq??null,
|
|
361
|
+
const formatted=formatLogLine(file,line,{auditEvent,activityTracker,sequence:recorded?.event?.seq??null,
|
|
362
|
+
sourceSequence:Number.isSafeInteger(auditEvent?.seq)?auditEvent.seq:null,verbose,displayMode});
|
|
362
363
|
if(!formatted||!accepted) continue;
|
|
363
364
|
const output=Array.isArray(formatted)?formatted:[formatted];
|
|
364
365
|
for(const item of output) if(item){
|
|
@@ -512,9 +513,9 @@ async function runRemoteSession({resumeCursor=null,verbose=false,trace=false,fil
|
|
|
512
513
|
filePath:remoteResultJournalPath(ctx.root,marker.result_session_id),sessionId:marker.result_session_id,maxEntries:2048
|
|
513
514
|
});
|
|
514
515
|
const rootPaths=await readLiveRootPaths(ctx.root);
|
|
515
|
-
const activityTracker=createLiveTimelineTracker({verbose,displayMode,rootPaths,trace});
|
|
516
|
+
const activityTracker=createLiveTimelineTracker({verbose,displayMode,rootPaths,trace,foldHeartbeats:!verbose});
|
|
516
517
|
const timelineFilter=filterExpression?createLiveTimelineFilter(filterExpression):null;
|
|
517
|
-
const timelineWriter=createLiveTimelineWriter({stream:process.stdout,displayMode,rewrite
|
|
518
|
+
const timelineWriter=createLiveTimelineWriter({stream:process.stdout,displayMode,rewrite:false});
|
|
518
519
|
const aborter=new AbortController();
|
|
519
520
|
const abort=()=>aborter.abort();
|
|
520
521
|
const watchStdin=!process.stdin.isTTY;
|
|
@@ -588,6 +589,8 @@ async function runRemoteSession({resumeCursor=null,verbose=false,trace=false,fil
|
|
|
588
589
|
if(logTask) await logTask.catch(()=>{});
|
|
589
590
|
timelineWriter.flush();
|
|
590
591
|
for(const line of activityTracker.reconcileInterrupted('session_end')) process.stdout.write(`${line}\n`);
|
|
592
|
+
const filterRetention=timelineFilter?.retention();
|
|
593
|
+
if(filterRetention?.evicted>0) process.stdout.write(`[WARN] LIVELOG filter evicted ${filterRetention.evicted} presentation identities; full audit remains on disk.\n`);
|
|
591
594
|
process.stdout.write(`${activityTracker.summary()}\n`);
|
|
592
595
|
process.removeListener('SIGINT',abort);process.removeListener('SIGTERM',abort);
|
|
593
596
|
if(watchStdin){process.stdin.removeListener('end',abort);process.stdin.removeListener('close',abort);process.stdin.pause();}
|
|
@@ -1,10 +1,37 @@
|
|
|
1
1
|
import {lstat,opendir,readFile} from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import {createHash} from 'node:crypto';
|
|
3
|
+
import {createHash,createPublicKey} from 'node:crypto';
|
|
4
4
|
import {resolveStateRoot} from './state-root.mjs';
|
|
5
|
+
import {verifySlotUpdateEnvelope} from './runtime-update.mjs';
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
const UPDATE_RECEIPT='deadbyte.r27-slot-update-receipt.v1';
|
|
8
|
+
const FAILURE_RECEIPT='deadbyte.r27-slot-update-failure.v1';
|
|
9
|
+
const ROLLBACK_RECEIPT='deadbyte.r31-slot-rollback-receipt.v1';
|
|
10
|
+
const ROLLBACK_FAILURE_RECEIPT='deadbyte.r31-slot-rollback-failure.v1';
|
|
11
|
+
|
|
12
|
+
async function readReceipt(file){
|
|
13
|
+
const info=await lstat(file);
|
|
14
|
+
if(!info.isFile()||info.isSymbolicLink()||info.size>1048576)throw new Error('unsafe transaction receipt');
|
|
15
|
+
const bytes=await readFile(file);
|
|
16
|
+
if(bytes.length>1048576)throw new Error('transaction receipt budget');
|
|
17
|
+
return JSON.parse(bytes.toString('utf8').replace(/^\uFEFF/,''));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function receiptSubjectMatches(state,envelope,kind){
|
|
21
|
+
const body=envelope?.body;
|
|
22
|
+
if(state.schema==='deadbyte.r27-slot-update-state.v1'){
|
|
23
|
+
if(kind==='success')return body?.schema===UPDATE_RECEIPT&&body?.update_id===state.update_id&&body?.rollback_performed===false;
|
|
24
|
+
return body?.schema===FAILURE_RECEIPT&&body?.update_id===state.update_id;
|
|
25
|
+
}
|
|
26
|
+
if(state.schema==='deadbyte.r31-slot-rollback-state.v1'){
|
|
27
|
+
if(kind==='success')return body?.schema===ROLLBACK_RECEIPT&&body?.rollback_id===state.rollback_id&&body?.rollback_performed===true;
|
|
28
|
+
return body?.schema===ROLLBACK_FAILURE_RECEIPT&&body?.rollback_id===state.rollback_id;
|
|
29
|
+
}
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function inspectDoctorTransactions(explicitRoot,{maxEntries=1024}={}){
|
|
34
|
+
if(!Number.isSafeInteger(maxEntries)||maxEntries<1||maxEntries>1024)throw new Error('doctor transaction budget invalid');
|
|
8
35
|
const state=resolveStateRoot(explicitRoot),root=path.join(state,'evidence','updates'),findings=[];
|
|
9
36
|
const next='Retain transaction evidence; inspect the exact signed receipt/handoff and current pointer before any supported recovery. Doctor never launches recovery.';
|
|
10
37
|
const add=(level,what,evidence)=>findings.push({id:'transaction_residue',level,what,evidence,next_action:level==='PASS'?null:next});
|
|
@@ -17,24 +44,68 @@ export async function inspectDoctorTransactions(explicitRoot,{maxEntries=256}={}
|
|
|
17
44
|
else add('FAIL','Update evidence directory cannot be safely inspected.',{error_code:error?.code??'VALIDATION_FAILED'});
|
|
18
45
|
return {status:findings[0].level,findings};
|
|
19
46
|
}
|
|
20
|
-
let
|
|
47
|
+
let publicKey=null;
|
|
48
|
+
const loadPublicKey=async()=>{
|
|
49
|
+
if(publicKey)return publicKey;
|
|
50
|
+
const keyFile=path.join(state,'trust','ed25519-public.pem');
|
|
51
|
+
const info=await lstat(keyFile);
|
|
52
|
+
if(!info.isFile()||info.isSymbolicLink()||info.size>65536)throw new Error('unsafe transaction trust key');
|
|
53
|
+
publicKey=createPublicKey(await readFile(keyFile));
|
|
54
|
+
if(publicKey.asymmetricKeyType!=='ed25519')throw new Error('transaction trust key must be Ed25519');
|
|
55
|
+
return publicKey;
|
|
56
|
+
};
|
|
57
|
+
let entries=0,checked=0,terminal=0,verifiedTerminalReceipts=0,verifiedSuccessReceipts=0,verifiedFailureReceipts=0,unresolved=0,inventoryComplete=true;
|
|
21
58
|
try{
|
|
22
59
|
for await(const entry of directory){
|
|
23
|
-
if(++entries>maxEntries){add('WARN','Transaction inventory budget reached; unobserved entries remain.',{max_entries:maxEntries,states_checked:checked,inventory_complete:false});break;}
|
|
60
|
+
if(++entries>maxEntries){inventoryComplete=false;add('WARN','Transaction inventory budget reached; unobserved entries remain.',{max_entries:maxEntries,states_checked:checked,inventory_complete:false});break;}
|
|
24
61
|
if(entry.isSymbolicLink()){add('FAIL','Transaction evidence contains a link; it was not followed.',{entry_name_sha256:createHash('sha256').update(entry.name).digest('hex')});continue;}
|
|
25
62
|
if(!entry.isDirectory())continue;
|
|
26
|
-
const
|
|
63
|
+
const dir=path.join(root,entry.name),file=path.join(dir,'state.json');let bytes;
|
|
27
64
|
try{
|
|
28
65
|
const info=await lstat(file);if(!info.isFile()||info.isSymbolicLink()||info.size>1048576)throw new Error('unsafe transaction state');
|
|
29
66
|
bytes=await readFile(file);if(bytes.length>1048576)throw new Error('transaction state budget');
|
|
30
67
|
const body=JSON.parse(bytes.toString('utf8').replace(/^\uFEFF/,''));checked++;
|
|
31
68
|
const digest=createHash('sha256').update(bytes).digest('hex');
|
|
32
69
|
if(!['deadbyte.r27-slot-update-state.v1','deadbyte.r31-slot-rollback-state.v1'].includes(body?.schema)||typeof body.phase!=='string'||!/^[a-z_]{1,64}$/.test(body.phase)){add('FAIL','Transaction state schema or phase is invalid.',{state_sha256:digest,check:'transaction_schema'});continue;}
|
|
33
|
-
|
|
70
|
+
const receiptPath=path.join(dir,'receipt.json'),failurePath=path.join(dir,'failure-receipt.json');
|
|
71
|
+
let receiptExists=false,failureExists=false;
|
|
72
|
+
try{const st=await lstat(receiptPath);receiptExists=st.isFile()&&!st.isSymbolicLink();}catch(error){if(error?.code!=='ENOENT')throw error;}
|
|
73
|
+
try{const st=await lstat(failurePath);failureExists=st.isFile()&&!st.isSymbolicLink();}catch(error){if(error?.code!=='ENOENT')throw error;}
|
|
74
|
+
if(receiptExists&&failureExists){add('FAIL','Transaction contains conflicting terminal receipts.',{state_sha256:digest,check:'terminal_receipt_conflict'});continue;}
|
|
75
|
+
const terminalPath=receiptExists?receiptPath:failureExists?failurePath:null;
|
|
76
|
+
if(terminalPath){
|
|
77
|
+
const kind=receiptExists?'success':'failure';
|
|
78
|
+
const envelope=await readReceipt(terminalPath);
|
|
79
|
+
const key=await loadPublicKey();
|
|
80
|
+
if(!verifySlotUpdateEnvelope(envelope,key)||!receiptSubjectMatches(body,envelope,kind)){
|
|
81
|
+
add('FAIL','Transaction terminal receipt is invalid or not subject-bound to its journal.',{state_sha256:digest,check:'terminal_receipt_verification',receipt_kind:kind});
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
terminal++;verifiedTerminalReceipts++;
|
|
85
|
+
if(kind==='success')verifiedSuccessReceipts++;else verifiedFailureReceipts++;
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if(body.phase==='superseded'){terminal++;continue;}
|
|
90
|
+
if(body.phase==='failed_receipted'){
|
|
91
|
+
add('FAIL','Transaction journal claims a signed failure receipt but the receipt is missing.',{state_sha256:digest,check:'missing_failure_receipt',recorded_phase:body.phase});
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
unresolved++;
|
|
34
95
|
add('WARN','Nonterminal or unresolved transaction record remains; liveness/outcome is not inferred from its age or PID.',{state_sha256:digest,schema:body.schema,recorded_phase:body.phase});
|
|
35
96
|
}catch(error){if(error?.code==='ENOENT')continue;add('FAIL','Transaction state cannot be safely parsed or inspected.',{error_code:error?.code??'VALIDATION_FAILED'});}
|
|
36
97
|
}
|
|
37
98
|
}catch(error){add('FAIL','Transaction inventory observation failed.',{error_code:error?.code??'OBSERVATION_FAILED'});}
|
|
38
|
-
add('PASS','Recorded terminal transaction
|
|
99
|
+
add('PASS','Recorded terminal transaction outcomes counted; signed receipts are verified when they reconcile stale journal phases.',{
|
|
100
|
+
states_checked:checked,
|
|
101
|
+
recorded_terminal_states:terminal,
|
|
102
|
+
verified_terminal_receipts:verifiedTerminalReceipts,
|
|
103
|
+
verified_success_receipts:verifiedSuccessReceipts,
|
|
104
|
+
verified_failure_receipts:verifiedFailureReceipts,
|
|
105
|
+
unresolved_transactions:unresolved,
|
|
106
|
+
inventory_complete:inventoryComplete,
|
|
107
|
+
max_entries:maxEntries,
|
|
108
|
+
receipt_verification_performed:true
|
|
109
|
+
});
|
|
39
110
|
return {status:findings.some(f=>f.level==='FAIL')?'FAIL':findings.some(f=>f.level==='WARN')?'WARN':'PASS',findings};
|
|
40
111
|
}
|
package/src/remote-live-log.mjs
CHANGED
|
@@ -275,6 +275,8 @@ function toolFilterMatch(pattern,tool){
|
|
|
275
275
|
export function createLiveTimelineFilter(expression=''){
|
|
276
276
|
const spec=parseFilterExpression(expression);
|
|
277
277
|
const pending=new Map();
|
|
278
|
+
const MAX_FILTER_IDENTITIES=4096;
|
|
279
|
+
let evicted=0;
|
|
278
280
|
const baseMatch=meta=>(!spec.tool||toolFilterMatch(spec.tool,meta.tool))&&(!spec.category||spec.category===meta.category)&&(!spec.trace||spec.trace===meta.trace_id);
|
|
279
281
|
return Object.freeze({
|
|
280
282
|
accept(event){
|
|
@@ -284,6 +286,8 @@ export function createLiveTimelineFilter(expression=''){
|
|
|
284
286
|
if(event.event_type==='tool.request'){
|
|
285
287
|
const args=event.args??{};const tool=displayToolName(rawTool,args);const trace=traceIdentity(event,args);
|
|
286
288
|
const meta={tool,category:categoryForTool(tool),trace_id:trace.trace_id};
|
|
289
|
+
if(pending.has(requestId))pending.delete(requestId);
|
|
290
|
+
if(pending.size>=MAX_FILTER_IDENTITIES){pending.delete(pending.keys().next().value);evicted++;}
|
|
287
291
|
pending.set(requestId,meta);
|
|
288
292
|
return spec.status?false:baseMatch(meta);
|
|
289
293
|
}
|
|
@@ -294,7 +298,8 @@ export function createLiveTimelineFilter(expression=''){
|
|
|
294
298
|
}
|
|
295
299
|
return false;
|
|
296
300
|
},
|
|
297
|
-
spec
|
|
301
|
+
spec,
|
|
302
|
+
retention(){return Object.freeze({pending:pending.size,evicted});}
|
|
298
303
|
});
|
|
299
304
|
}
|
|
300
305
|
export function createLiveTimelineWriter({stream=process.stdout,displayMode='unicode',rewrite=true}={}){
|
|
@@ -330,10 +335,12 @@ export function createLiveTimelineTracker({clock=()=>Date.now(),verbose=false,fo
|
|
|
330
335
|
if(!Number.isInteger(slowCallMs)||slowCallMs<100||slowCallMs>600000) throw new Error('live timeline slowCallMs invalid');
|
|
331
336
|
const pending=new Map();
|
|
332
337
|
const completed=new Set();
|
|
338
|
+
const MAX_PRESENTATION_IDENTITIES=4096;
|
|
333
339
|
const toolCounts=new Map();
|
|
334
340
|
const durationSamples=[];
|
|
335
341
|
let resultCount=0,okCount=0,errorCount=0,unknownCount=0,mutationCount=0,totalDurationMs=0,slowest=null,burst=null,foldedHeartbeatLines=0,peakInFlight=0;
|
|
336
342
|
let lastCapability=null,suppressedCapability=0,suppressedCapabilityReported=false;
|
|
343
|
+
let lastSourceSequence=0,pendingOverflow=0;
|
|
337
344
|
|
|
338
345
|
function eventPrefix(event,sequence,{displayTool=null,icon=null}={}){
|
|
339
346
|
const seq=Number.isSafeInteger(sequence)&&sequence>0?'#'+sequence+' ':'';
|
|
@@ -362,10 +369,12 @@ export function createLiveTimelineTracker({clock=()=>Date.now(),verbose=false,fo
|
|
|
362
369
|
error?(displayMode==='ascii'?'ERR':'✗'):
|
|
363
370
|
['write','edit','run'].includes(category)?(displayMode==='ascii'?'SUCCEEDED':'✅ SUCCEEDED'):
|
|
364
371
|
(displayMode==='ascii'?'OK':'✓');
|
|
372
|
+
const rawStatus=safeAtom(event.outcome?.status);
|
|
373
|
+
const statusDetail=rawStatus&&!['ok','error'].includes(rawStatus)?' status='+rawStatus:'';
|
|
365
374
|
const prefixIcon=unknown?(displayMode==='ascii'?'WARN':'⚠️'):null;
|
|
366
375
|
const evidence=receipt?' '+(displayMode==='ascii'?'RECEIPT':'🧾 receipt')+(displayMode==='ascii'?'=':'=')+receipt:'';
|
|
367
376
|
const reasonSep=displayMode==='ascii'?' - ':' — ';
|
|
368
|
-
return remoteDisplayText(eventPrefix(event,sequence,{displayTool,icon:prefixIcon})+traceSuffix(span?.trace_meta)+(summary?' '+summary:'')+(duration?' '+duration:'')+slow+' '+marker+evidence+(reason?reasonSep+reason:''),displayMode);
|
|
377
|
+
return remoteDisplayText(eventPrefix(event,sequence,{displayTool,icon:prefixIcon})+traceSuffix(span?.trace_meta)+(summary?' '+summary:'')+(duration?' '+duration:'')+slow+' '+marker+statusDetail+evidence+(reason?reasonSep+reason:''),displayMode);
|
|
369
378
|
}
|
|
370
379
|
function burstLine(value){
|
|
371
380
|
if(!value) return null;
|
|
@@ -386,7 +395,8 @@ export function createLiveTimelineTracker({clock=()=>Date.now(),verbose=false,fo
|
|
|
386
395
|
function addStats(event,span){
|
|
387
396
|
resultCount++;
|
|
388
397
|
const displayTool=span?.display_tool||event.tool;
|
|
389
|
-
toolCounts.
|
|
398
|
+
const countKey=toolCounts.has(displayTool)||toolCounts.size<256?displayTool:'[other]';
|
|
399
|
+
toolCounts.set(countKey,(toolCounts.get(countKey)??0)+1);
|
|
390
400
|
const outcome=outcomeClass(event);
|
|
391
401
|
if(outcome==='unknown') unknownCount++;else if(outcome==='error') errorCount++;else okCount++;
|
|
392
402
|
if(['write','edit','run'].includes(categoryForTool(displayTool))) mutationCount++;
|
|
@@ -397,8 +407,12 @@ export function createLiveTimelineTracker({clock=()=>Date.now(),verbose=false,fo
|
|
|
397
407
|
if(!slowest||duration>slowest.duration_ms) slowest={tool:displayTool,duration_ms:duration};
|
|
398
408
|
}
|
|
399
409
|
}
|
|
400
|
-
function observe(event,{sequence=null}={}){
|
|
410
|
+
function observe(event,{sequence=null,sourceSequence=null}={}){
|
|
401
411
|
if(!event||typeof event!=='object'||Array.isArray(event)) return null;
|
|
412
|
+
if(Number.isSafeInteger(sourceSequence)&&sourceSequence>0){
|
|
413
|
+
if(sourceSequence<=lastSourceSequence)return null;
|
|
414
|
+
lastSourceSequence=sourceSequence;
|
|
415
|
+
}
|
|
402
416
|
const requestId=typeof event.request_id==='string'&&event.request_id?event.request_id:null;
|
|
403
417
|
const tool=typeof event.tool==='string'&&event.tool?event.tool:null;
|
|
404
418
|
if(event.event_type==='tool.request'&&requestId&&tool){
|
|
@@ -407,10 +421,17 @@ export function createLiveTimelineTracker({clock=()=>Date.now(),verbose=false,fo
|
|
|
407
421
|
const displayTool=displayToolName(tool,args);
|
|
408
422
|
const summary=summarizeLiveToolArgs(displayTool,args,{rootPaths});
|
|
409
423
|
const traceMeta=traceIdentity(event,args);
|
|
424
|
+
if(pending.size>=MAX_PRESENTATION_IDENTITIES){
|
|
425
|
+
pendingOverflow++;
|
|
426
|
+
return pendingOverflow===1?remoteDisplayText('LIVELOG pending span cap reached; full audit remains on disk',displayMode):null;
|
|
427
|
+
}
|
|
410
428
|
pending.set(requestId,{request_id:requestId,tool,display_tool:displayTool,summary,trace_meta:traceMeta,started_at_ms:clock()});
|
|
411
429
|
peakInFlight=Math.max(peakInFlight,pending.size);
|
|
412
430
|
if(verbose) return formatRemoteToolCall({...event,tool:displayTool},{clock,displayMode})+traceSuffix(traceMeta);
|
|
413
|
-
if(visibleStart(displayTool))
|
|
431
|
+
if(visibleStart(displayTool)){
|
|
432
|
+
const startMarker=displayMode==='ascii'?'START':'▶ START';
|
|
433
|
+
return remoteDisplayText(eventPrefix(event,sequence,{displayTool})+traceSuffix(traceMeta)+' '+startMarker+(summary?' '+summary:''),displayMode);
|
|
434
|
+
}
|
|
414
435
|
return null;
|
|
415
436
|
}
|
|
416
437
|
if(event.event_type==='tool.result'&&requestId&&tool){
|
|
@@ -418,6 +439,7 @@ export function createLiveTimelineTracker({clock=()=>Date.now(),verbose=false,fo
|
|
|
418
439
|
const span=pending.get(requestId)??null;
|
|
419
440
|
pending.delete(requestId);
|
|
420
441
|
completed.add(requestId);
|
|
442
|
+
if(completed.size>MAX_PRESENTATION_IDENTITIES)completed.delete(completed.values().next().value);
|
|
421
443
|
addStats(event,span);
|
|
422
444
|
const error=event.outcome?.is_error===true;
|
|
423
445
|
const nowMs=eventMs(event,clock);
|
|
@@ -487,6 +509,7 @@ export function createLiveTimelineTracker({clock=()=>Date.now(),verbose=false,fo
|
|
|
487
509
|
}
|
|
488
510
|
return Object.freeze({
|
|
489
511
|
observe,
|
|
512
|
+
retention(){return Object.freeze({pending:pending.size,completed:completed.size,pending_overflow:pendingOverflow,last_source_sequence:lastSourceSequence});},
|
|
490
513
|
pending(){return [...pending.values()].map(({request_id,tool})=>({request_id,tool}));},
|
|
491
514
|
reconcileInterrupted,
|
|
492
515
|
summary
|
package/src/runtime-update.mjs
CHANGED
|
@@ -26,6 +26,7 @@ const UPDATE_SCHEMA='deadbyte.r27-slot-update-receipt.v1';
|
|
|
26
26
|
const HANDOFF_SCHEMA='deadbyte.r27-slot-update-handoff.v1';
|
|
27
27
|
const FAILURE_SCHEMA='deadbyte.r27-slot-update-failure.v1';
|
|
28
28
|
const ROLLBACK_SCHEMA='deadbyte.r31-slot-rollback-receipt.v1';
|
|
29
|
+
const ROLLBACK_FAILURE_SCHEMA='deadbyte.r31-slot-rollback-failure.v1';
|
|
29
30
|
const DEFAULT_STATE_ROOT=undefined;
|
|
30
31
|
const TOP_LAUNCHER=path.join(os.homedir(),'Downloads','DEADBYTE-MCP.cmd');
|
|
31
32
|
const RUNTIME_STABILITY_MS=12000;
|
|
@@ -394,6 +395,18 @@ export async function prepareBridgeSpec(config,bootstrap){
|
|
|
394
395
|
return next;
|
|
395
396
|
}
|
|
396
397
|
|
|
398
|
+
export async function prepareReleaseBridgeSpec(config,bootstrap,releaseRoot){
|
|
399
|
+
if(!bootstrap) return structuredClone(config);
|
|
400
|
+
if(typeof releaseRoot!=='string'||!path.isAbsolute(releaseRoot))throw new Error('immutable release root required for Bridge binding');
|
|
401
|
+
// Verify the requested development input, then bind runtime paths only to
|
|
402
|
+
// the manifest-verified immutable slot produced from that same candidate.
|
|
403
|
+
const verifiedSource=await prepareBridgeSpec(config,bootstrap);
|
|
404
|
+
return prepareBridgeSpec(verifiedSource,{
|
|
405
|
+
root:path.join(path.resolve(releaseRoot),'bridge'),
|
|
406
|
+
scriptSha256:verifiedSource.Bridge.ScriptSha256
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
|
|
397
410
|
export async function prepareAutonomousSupervisorSpec(config,{releaseRoot,enable=false}={}){
|
|
398
411
|
const next=structuredClone(config);
|
|
399
412
|
if(!enable) return next;
|
|
@@ -1682,7 +1695,7 @@ export async function performSlotUpdate({stage,developmentRoot,expectedVersion,e
|
|
|
1682
1695
|
if(!control.desktopPolicy) throw new Error('DesktopRuntimeEnabled requires pinned desktop policy bootstrap or existing policy');
|
|
1683
1696
|
control.config.DesktopRuntimeEnabled=true;
|
|
1684
1697
|
}
|
|
1685
|
-
control.config=await
|
|
1698
|
+
control.config=await prepareReleaseBridgeSpec(control.config,bridgeBootstrap,slot.release_root);
|
|
1686
1699
|
control.config=await prepareNamedTunnelCloudSpec(control.config,{releaseRoot:slot.release_root,bootstrap:cloudTransport});
|
|
1687
1700
|
control.config=await prepareAutonomousSupervisorSpec(control.config,{releaseRoot:slot.release_root,enable:enableAutonomousSupervisor});
|
|
1688
1701
|
authorityPreparation=await prepareAuthorityPolicies({hostPolicy:control.hostPolicy,codingPolicy:control.codingPolicy},{commandPins:hostCommandPins});
|
|
@@ -1750,7 +1763,7 @@ export async function performSlotRollback({activationReceipt,stateRoot=DEFAULT_S
|
|
|
1750
1763
|
const currentCtx=slotContext(state,pointerInfo.pointer),currentRuntime=await captureRuntime(currentCtx);
|
|
1751
1764
|
const revoked=await revokeAuthority(currentCtx,currentRuntime.status);
|
|
1752
1765
|
const rollbackId=`r31-rollback-${activationEnvelope.receipt_sha256.slice(0,16)}-${Date.now().toString(36)}-${randomBytes(4).toString('hex')}`;
|
|
1753
|
-
const evidenceDir=path.join(state,'evidence','updates',rollbackId),statePath=path.join(evidenceDir,'state.json'),receiptPath=path.join(evidenceDir,'receipt.json');
|
|
1766
|
+
const evidenceDir=path.join(state,'evidence','updates',rollbackId),statePath=path.join(evidenceDir,'state.json'),receiptPath=path.join(evidenceDir,'receipt.json'),failurePath=path.join(evidenceDir,'failure-receipt.json');
|
|
1754
1767
|
await mkdir(evidenceDir,{recursive:true});
|
|
1755
1768
|
const journal={schema:'deadbyte.r31-slot-rollback-state.v1',rollback_id:rollbackId,activation_receipt:path.resolve(receiptFile),
|
|
1756
1769
|
activation_receipt_sha256:activationEnvelope.receipt_sha256,handoff_path:handoffPath,
|
|
@@ -1795,8 +1808,19 @@ export async function performSlotRollback({activationReceipt,stateRoot=DEFAULT_S
|
|
|
1795
1808
|
}
|
|
1796
1809
|
try { await startAndVerify(currentCtx,currentRuntime); recovery.successor_runtime_restarted=true; } catch(recoveryError){ recovery.errors.push(`restart-successor:${recoveryError.message}`); }
|
|
1797
1810
|
}
|
|
1798
|
-
|
|
1799
|
-
|
|
1811
|
+
const failure={schema:ROLLBACK_FAILURE_SCHEMA,rollback_id:rollbackId,activation_receipt_sha256:activationEnvelope.receipt_sha256,
|
|
1812
|
+
failed_at_utc:new Date().toISOString(),phase:journal.phase??'unknown',from_pointer:publicPointer(activePointer),to_pointer:publicPointer(priorPointer),
|
|
1813
|
+
error:{name:error?.name??'Error',message:String(error?.message??error)},recovery};
|
|
1814
|
+
await writeState(statePath,journal,'failed',{error:failure.error,recovery}).catch(()=>{});
|
|
1815
|
+
let failureReceipt=null;
|
|
1816
|
+
try{
|
|
1817
|
+
failureReceipt=await writeEnvelope(failurePath,failure,key);
|
|
1818
|
+
await writeState(statePath,journal,'failed_receipted',{failure_receipt_path:failurePath,failure_receipt_sha256:failureReceipt.receipt_sha256});
|
|
1819
|
+
}catch(receiptError){
|
|
1820
|
+
recovery.errors.push('failure-receipt:'+receiptError.message);
|
|
1821
|
+
await writeState(statePath,journal,'failed_unreceipted',{recovery,failure_receipt_error:String(receiptError?.message??receiptError)}).catch(()=>{});
|
|
1822
|
+
}
|
|
1823
|
+
const wrapped=new Error(`R31 slot rollback failed: ${error?.message??error}`); wrapped.cause=error; wrapped.statePath=statePath; wrapped.failureReceiptPath=failureReceipt?failurePath:null; throw wrapped;
|
|
1800
1824
|
}
|
|
1801
1825
|
}
|
|
1802
1826
|
|
package/src/version.mjs
CHANGED