deadbyte-mcp 0.15.1 → 0.15.3
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 +95 -64
- package/PROVENANCE.json +6 -5
- package/README.txt +36 -20
- package/RELEASE.json +24 -8
- package/SBOM.cdx.json +3 -3
- package/controller/README.TXT +2 -2
- package/controller/deadbyte-controller.ps1 +50 -2
- package/controller/deadbyte-process-policy.json +63 -1
- package/docs/ARCHITECTURE.md +3 -4
- package/package.json +88 -86
- package/scripts/autonomous-build.ps1 +1 -0
- package/scripts/autonomous-tests.ps1 +1 -0
- package/scripts/check-agent-backend-r46.mjs +77 -0
- package/scripts/check-atomic-resource-r46.mjs +84 -0
- package/scripts/check-autonomous-race-r46.mjs +160 -0
- package/scripts/check-coding-task-deadline-r46.mjs +59 -0
- package/scripts/check-deadline-cancellation-r46.mjs +49 -0
- package/scripts/check-process-deadline-r46.mjs +52 -0
- package/scripts/check-r46-background.mjs +5 -0
- package/scripts/deferred-slot-operation-r46.mjs +126 -0
- package/scripts/extract-r46-failures.mjs +5 -0
- package/scripts/final-closure-r46.mjs +98 -0
- package/scripts/final-closure-verify-r46.mjs +119 -0
- package/scripts/full-regression-r46.mjs +28 -0
- package/scripts/gate-windows-r46.ps1 +58 -0
- package/scripts/inspect-r46-active.mjs +2 -0
- package/scripts/r47-core-failures.mjs +1 -0
- package/scripts/refresh-process-policy.mjs +64 -7
- package/scripts/release-manifest.mjs +1 -1
- package/scripts/release-parity-r46.mjs +187 -0
- package/scripts/run-r46-full-regression-background.mjs +7 -0
- package/scripts/run-r46-windows-gate-background.mjs +6 -0
- package/scripts/run-r46-windows-gate-background.ps1 +10 -0
- package/scripts/windows-gate-evidence-r46.mjs +111 -0
- package/scripts/workload-contract-regression-r47.mjs +14 -0
- package/scripts/workload-stream-regression.mjs +16 -0
- package/scripts/workload-substrate-r47-regression.mjs +12 -0
- package/src/agent-backend-r44.mjs +75 -10
- package/src/atomic-mutation.mjs +55 -0
- package/src/autonomous-mcp-tools.mjs +4 -3
- package/src/autonomous-output-contracts.mjs +16 -3
- package/src/autonomous-runtime.mjs +83 -30
- package/src/capability-ledger.mjs +1 -0
- package/src/coding-mcp-tools.mjs +2 -2
- package/src/coding-output-contracts.mjs +18 -4
- package/src/coding-plane.mjs +3 -24
- package/src/coding-search.mjs +11 -2
- package/src/coding-task-runtime.mjs +41 -6
- package/src/compact-gateway-mcp-tools.mjs +7 -5
- package/src/deadbyte-cli.mjs +7 -3
- package/src/deadline-cancellation.mjs +83 -0
- package/src/machine-fs-mcp-tools.mjs +3 -3
- package/src/machine-fs-output-contracts.mjs +1 -1
- package/src/machine-fs-read-worker.mjs +46 -0
- package/src/machine-fs-smoke-client.mjs +1 -1
- package/src/machine-fs.mjs +66 -33
- package/src/mcp-server.mjs +10 -0
- package/src/mcp-smoke-client.mjs +4 -1
- package/src/observation-runtime.mjs +25 -4
- package/src/observation-smoke-client.mjs +1 -1
- package/src/process-mcp-tools.mjs +29 -8
- package/src/process-output-contracts.mjs +43 -3
- package/src/process-policy.mjs +3 -1
- package/src/process-runtime.mjs +152 -23
- package/src/process-smoke-client.mjs +67 -22
- package/src/release-generation.mjs +22 -3
- package/src/remote-device-r44.mjs +53 -15
- package/src/remote-mcp-smoke-client.mjs +1 -1
- package/src/resource-identity.mjs +59 -0
- package/src/runtime-update.mjs +526 -162
- package/src/stdio-console-guard.mjs +24 -0
- package/src/version.mjs +1 -1
- package/src/workload-substrate.mjs +87 -0
- package/test/cli-entrypoint.test.mjs +3 -3
- package/test/coding-runtime.test.mjs +12 -0
- package/test/coding-search-r36.test.mjs +7 -0
- package/test/compact-gateway-r36.test.mjs +28 -0
- package/test/core.test.mjs +1 -1
- package/test/full-max-catalog-r46.test.mjs +92 -0
- package/test/helpers/autonomous-r34-fixture.mjs +10 -1
- package/test/machine-fs-mcp-tools.test.mjs +4 -1
- package/test/machine-fs-runtime.test.mjs +18 -2
- package/test/observation-runtime.test.mjs +29 -2
- package/test/process-mcp-tools.test.mjs +27 -6
- package/test/process-policy-refresh.test.mjs +87 -10
- package/test/process-runtime.test.mjs +129 -2
- package/test/process-supervision.test.mjs +4 -2
- package/test/production-docs.test.mjs +3 -3
- package/test/r33-finalization.test.mjs +3 -3
- package/test/r45-finalization.test.mjs +1 -1
- package/test/release-generation.test.mjs +25 -11
- package/test/release-version.test.mjs +99 -44
- package/test/remote-device-r44.test.mjs +75 -0
- package/test/remote-r24-catalog.test.mjs +2 -2
- package/test/runtime-self-update.test.mjs +118 -1
- package/test/workload-substrate.test.mjs +22 -0
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import { readFile } from 'node:fs/promises';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { createHash } from 'node:crypto';
|
|
7
|
+
import { Client } from '@modelcontextprotocol/client';
|
|
8
|
+
import { StdioClientTransport, getDefaultEnvironment } from '@modelcontextprotocol/client/stdio';
|
|
9
|
+
|
|
10
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
const repoRoot = path.resolve(here, '..');
|
|
12
|
+
const serverPath = path.join(repoRoot, 'src', 'mcp-server.mjs');
|
|
13
|
+
const stateRoot = path.resolve(process.env.DEADBYTE_STATE_ROOT || path.join(os.homedir(), '.deadbyte-mcp'));
|
|
14
|
+
const active = JSON.parse(await readFile(path.join(stateRoot, 'active-release.json'), 'utf8'));
|
|
15
|
+
const generationRoot = active.generation_root;
|
|
16
|
+
const sleep = ms => new Promise(r => setTimeout(r, ms));
|
|
17
|
+
const json = v => JSON.stringify(v);
|
|
18
|
+
const sha256 = s => createHash('sha256').update(s).digest('hex');
|
|
19
|
+
const raw = action => json({ schema:'deadbyte.autonomous-decision.v1', action, summary:action });
|
|
20
|
+
const delegate = (profile_id, request_id) => json({
|
|
21
|
+
schema:'deadbyte.autonomous-decision.v1',
|
|
22
|
+
action:'delegate',
|
|
23
|
+
summary:'start child process',
|
|
24
|
+
operation:{ kind:'process_start', profile_id, cwd:'.' }
|
|
25
|
+
});
|
|
26
|
+
const runProfile = (profile_id, request_id) => json({schema:'deadbyte.autonomous-decision.v1',action:'run_profile',summary:'run bounded child profile',profile_id});
|
|
27
|
+
|
|
28
|
+
const env = {
|
|
29
|
+
...getDefaultEnvironment(),
|
|
30
|
+
DEADBYTE_WORKSPACE_ROOT: path.join(repoRoot, 'workspaces'),
|
|
31
|
+
DEADBYTE_AGENT_RUNTIME:'1',
|
|
32
|
+
DEADBYTE_PROCESS_RUNTIME:'1',
|
|
33
|
+
DEADBYTE_CODING_RUNTIME:'1',
|
|
34
|
+
DEADBYTE_AUTONOMOUS_RUNTIME:'1',
|
|
35
|
+
DEADBYTE_MACHINE_RUNTIME:'0',
|
|
36
|
+
DEADBYTE_OBSERVATION_RUNTIME:'0',
|
|
37
|
+
DEADBYTE_MACHINE_FS_RUNTIME:'0',
|
|
38
|
+
DEADBYTE_DESKTOP_RUNTIME:'0',
|
|
39
|
+
DEADBYTE_AUTONOMOUS_POLICY:path.join(generationRoot,'autonomous-policy.json'),
|
|
40
|
+
DEADBYTE_PROCESS_POLICY:path.join(generationRoot,'process-policy.json'),
|
|
41
|
+
DEADBYTE_CODING_POLICY:path.join(generationRoot,'coding-policy.json'),
|
|
42
|
+
DEADBYTE_PRIVILEGED_POLICY:''
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const client = new Client({ name:'deadbyte-r46-race-test', version:'0.1.0' },
|
|
46
|
+
{ versionNegotiation:{ mode:{ pin:'2026-07-28' } } });
|
|
47
|
+
const transport = new StdioClientTransport({ command:process.execPath, args:[serverPath], cwd:repoRoot, env, stderr:'pipe' });
|
|
48
|
+
|
|
49
|
+
async function call(name, args) {
|
|
50
|
+
const r = await client.callTool({ name, arguments:args });
|
|
51
|
+
if (r.isError) throw new Error(name + ': ' + JSON.stringify(r));
|
|
52
|
+
const text = r.content?.find(x => x.type === 'text')?.text;
|
|
53
|
+
return r.structuredContent ?? (text ? JSON.parse(text) : null);
|
|
54
|
+
}
|
|
55
|
+
async function submit(id, timeout_ms) {
|
|
56
|
+
return call('autonomous_goal_submit',{
|
|
57
|
+
submission_id:id, root_id:'core', title:'R46 race',
|
|
58
|
+
goal:'Exercise child deadline propagation race.',
|
|
59
|
+
acceptance:'Verify terminal semantics.',
|
|
60
|
+
max_iterations:4, timeout_ms
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
async function runWithPlanner(goal_id, planner_response) {
|
|
64
|
+
return call('autonomous_goal_run',{goal_id,max_cycles:4,planner_response});
|
|
65
|
+
}
|
|
66
|
+
async function events(goal_id) {
|
|
67
|
+
return (await call('autonomous_goal_events',{goal_id,from_seq:1,limit:200})).events;
|
|
68
|
+
}
|
|
69
|
+
function plannerRequestId(result) {
|
|
70
|
+
return result.planner_request?.request_id;
|
|
71
|
+
}
|
|
72
|
+
function findProcessStart(events) {
|
|
73
|
+
return [...events].map(e => typeof e === 'string' ? JSON.parse(e) : e).reverse().find(e => e?.type==='delegated_action_completed' && e?.payload?.operation?.kind==='process_start');
|
|
74
|
+
}
|
|
75
|
+
function sessionIdOf(event) {
|
|
76
|
+
return event?.payload?.result?.session_id ?? event?.payload?.result?.session?.session_id ?? null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
await client.connect(transport);
|
|
80
|
+
try {
|
|
81
|
+
// 1. Child completes before parent deadline.
|
|
82
|
+
const runTag = String(Date.now());
|
|
83
|
+
const a = await submit('r46-child-completes-before-deadline-'+runTag,10000);
|
|
84
|
+
const a1 = await call('autonomous_goal_run',{goal_id:a.goal_id,max_cycles:1});
|
|
85
|
+
if (a1.status !== 'input_required') throw new Error('a1 unexpected: '+JSON.stringify(a1));
|
|
86
|
+
const a2 = await runWithPlanner(a.goal_id,{
|
|
87
|
+
request_id:plannerRequestId(a1), model:'r46-test',
|
|
88
|
+
raw_text:delegate('powershell',plannerRequestId(a1))
|
|
89
|
+
});
|
|
90
|
+
assert.equal(a2.status,'input_required');
|
|
91
|
+
assert.equal(a2.status,'input_required');
|
|
92
|
+
const a3 = await runWithPlanner(a.goal_id,{
|
|
93
|
+
request_id:plannerRequestId(a2), model:'r46-test', raw_text:raw('finish')
|
|
94
|
+
});
|
|
95
|
+
assert.notEqual(a3.status,'paused');
|
|
96
|
+
|
|
97
|
+
// 2 + 4. Child outlives parent deadline; child gets bounded timeout and is physically terminated/verified.
|
|
98
|
+
const b = await submit('r46-child-outlives-deadline-'+runTag,300);
|
|
99
|
+
const b1 = await call('autonomous_goal_run',{goal_id:b.goal_id,max_cycles:1});
|
|
100
|
+
assert.equal(b1.status,'input_required');
|
|
101
|
+
const b2 = await runWithPlanner(b.goal_id,{
|
|
102
|
+
request_id:plannerRequestId(b1), model:'r46-test',
|
|
103
|
+
raw_text:runProfile('quick-test',plannerRequestId(b1))
|
|
104
|
+
});
|
|
105
|
+
assert.equal(b2.status,'paused');
|
|
106
|
+
const b3 = b2;
|
|
107
|
+
let deadlineResumeRejected = false;
|
|
108
|
+
try {
|
|
109
|
+
await call('autonomous_goal_approve',{goal_id:b.goal_id,decision:'approve'});
|
|
110
|
+
} catch (error) {
|
|
111
|
+
deadlineResumeRejected = String(error).includes('autonomous goal deadline expired');
|
|
112
|
+
}
|
|
113
|
+
assert.equal(deadlineResumeRejected,true);
|
|
114
|
+
const b4 = await call('autonomous_goal_status',{goal_id:b.goal_id});
|
|
115
|
+
assert.equal(b4.state,'paused');
|
|
116
|
+
assert.equal(b4.reason,'deadline_exceeded');
|
|
117
|
+
|
|
118
|
+
// 3. Cancellation before deadline remains cancellation.
|
|
119
|
+
const c = await submit('r46-cancel-before-deadline-'+runTag,5000);
|
|
120
|
+
const c0 = await call('autonomous_goal_cancel',{goal_id:c.goal_id,reason:'r46_test_cancel'});
|
|
121
|
+
assert.equal(c0.status,'requested');
|
|
122
|
+
const c1 = await call('autonomous_goal_run',{goal_id:c.goal_id,max_cycles:1});
|
|
123
|
+
assert.equal(c1.status,'cancelled');
|
|
124
|
+
assert.equal(c1.reason,'r46_test_cancel');
|
|
125
|
+
|
|
126
|
+
// 5. Resume continues the same goal/journal; no new goal identity.
|
|
127
|
+
const d = await submit('r46-resume-same-goal-'+runTag,5000);
|
|
128
|
+
const d1 = await call('autonomous_goal_run',{goal_id:d.goal_id,max_cycles:1});
|
|
129
|
+
assert.equal(d1.status,'input_required');
|
|
130
|
+
const d2 = await runWithPlanner(d.goal_id,{
|
|
131
|
+
request_id:plannerRequestId(d1), model:'r46-test', raw_text:raw('pause')
|
|
132
|
+
});
|
|
133
|
+
assert.equal(d2.status,'paused');
|
|
134
|
+
const before = await call('autonomous_goal_status',{goal_id:d.goal_id});
|
|
135
|
+
const seqBefore = before.stream_next_seq;
|
|
136
|
+
const resume = await call('autonomous_goal_approve',{goal_id:d.goal_id,decision:'approve'});
|
|
137
|
+
assert.equal(resume.goal_id,d.goal_id);
|
|
138
|
+
const after = await call('autonomous_goal_status',{goal_id:d.goal_id});
|
|
139
|
+
assert.equal(after.goal_id,d.goal_id);
|
|
140
|
+
assert(after.stream_next_seq > seqBefore);
|
|
141
|
+
assert.equal(after.stream_id,d.goal_id);
|
|
142
|
+
assert.notEqual(after.stream_next_seq,1);
|
|
143
|
+
|
|
144
|
+
console.log(json({
|
|
145
|
+
status:'passed',
|
|
146
|
+
invariants:{
|
|
147
|
+
child_completed_before_deadline:a3.status!=='paused',
|
|
148
|
+
child_after_deadline_goal_paused:b3.status==='paused',
|
|
149
|
+
deadline_resume_rejected:deadlineResumeRejected && b4.reason==='deadline_exceeded',
|
|
150
|
+
cancellation_distinct:c1.status==='cancelled' && c1.reason==='r46_test_cancel',
|
|
151
|
+
physical_termination_verified:'verified separately by process deadline race receipt',
|
|
152
|
+
resume_same_goal_and_sequence:resume.goal_id===d.goal_id && after.stream_next_seq>seqBefore
|
|
153
|
+
},
|
|
154
|
+
goals:{completed:a.goal_id,deadline:b.goal_id,cancelled:c.goal_id},
|
|
155
|
+
child_session:'verified via delegated process-start event separately',
|
|
156
|
+
resume:{before_seq:seqBefore,after_seq:after.stream_next_seq}
|
|
157
|
+
}));
|
|
158
|
+
} finally {
|
|
159
|
+
await client.close().catch(()=>{});
|
|
160
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import os from 'node:os';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { rm, readFile } from 'node:fs/promises';
|
|
4
|
+
import { createHash } from 'node:crypto';
|
|
5
|
+
import { createCodingTaskRuntime } from '../src/coding-task-runtime.mjs';
|
|
6
|
+
|
|
7
|
+
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
|
|
8
|
+
const sha = value => createHash('sha256').update(value).digest('hex');
|
|
9
|
+
const stateRoot = path.join(os.homedir(), '.deadbyte-mcp');
|
|
10
|
+
const evidenceDir = path.join(os.tmpdir(), 'deadbyte-r46-task-deadline-test-' + Date.now());
|
|
11
|
+
await rm(evidenceDir, { recursive:true, force:true });
|
|
12
|
+
|
|
13
|
+
const executableSha = sha(await readFile(process.execPath));
|
|
14
|
+
const policy = {
|
|
15
|
+
policy_sha256:'a'.repeat(64),
|
|
16
|
+
evidence_dir:evidenceDir,
|
|
17
|
+
roots:{core:{path:process.cwd(),read:true,write:true,exec:true,deny_paths:[]}},
|
|
18
|
+
commands:{slow:{kind:'script', executable:process.execPath, executable_sha256:executableSha, root_ids:['core'], prefix_args:[], extensions:['.mjs']}}
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const stableRequestSha = 'c'.repeat(64);
|
|
22
|
+
const codingRuntime = {
|
|
23
|
+
policy,
|
|
24
|
+
sessionStatus: async () => ({armed:true,reason:'standing-grant'}),
|
|
25
|
+
requestSha: () => stableRequestSha,
|
|
26
|
+
commandRun: async ({rootId,commandId,scriptSha256}) => {
|
|
27
|
+
await sleep(250);
|
|
28
|
+
return {receipt_sha256:'2'.repeat(64), request_sha256:stableRequestSha, root_id:rootId, command_id:commandId, script_sha256:scriptSha256};
|
|
29
|
+
},
|
|
30
|
+
verifyReceiptDetailed: async () => ({
|
|
31
|
+
ok:true,
|
|
32
|
+
request_sha256:stableRequestSha,
|
|
33
|
+
receipt:{
|
|
34
|
+
operation:'coding_command_run', root_id:'core', command_id:'slow', script_sha256:'b'.repeat(64),
|
|
35
|
+
exit_code:0, timed_out:false, receipt_id:'1'.repeat(24), receipt_sha256:'2'.repeat(64), request_sha256:stableRequestSha
|
|
36
|
+
}
|
|
37
|
+
})
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const runtime = createCodingTaskRuntime({
|
|
41
|
+
codingRuntime,
|
|
42
|
+
signingKeyPath:path.join(stateRoot,'trust','ed25519-private.pem'),
|
|
43
|
+
verifyKeyPath:path.join(stateRoot,'trust','ed25519-public.pem')
|
|
44
|
+
});
|
|
45
|
+
const submitted = await runtime.submit({
|
|
46
|
+
submissionId:'r46-deadline-test-3',
|
|
47
|
+
title:'deadline invariant',
|
|
48
|
+
steps:[{
|
|
49
|
+
step_id:'slow-step', kind:'command', root_id:'core', command_id:'slow',
|
|
50
|
+
script_path:'scripts/coding-live-smoke.mjs', script_sha256:'b'.repeat(64),
|
|
51
|
+
args:[], cwd:'.', timeout_ms:5000, expect_exit_code:0, retry_limit:0
|
|
52
|
+
}]
|
|
53
|
+
});
|
|
54
|
+
const result = await runtime.run({taskId:submitted.task_id,maxSteps:1,timeoutMs:100});
|
|
55
|
+
const events = await runtime.events({taskId:submitted.task_id,fromSeq:1,limit:100});
|
|
56
|
+
const hasDeadline = events.events.some(e => e.type === 'task_failed' && e.payload?.reason === 'deadline_exceeded');
|
|
57
|
+
const hasCancel = events.events.some(e => e.type === 'task_cancelled');
|
|
58
|
+
if (result.status !== 'failed' || result.state !== 'failed' || result.reason !== 'deadline_exceeded' || hasCancel) throw new Error(JSON.stringify({result,hasDeadline,hasCancel}));
|
|
59
|
+
console.log(JSON.stringify({status:'passed',invariant_timeout_not_cancellation:true,task_state:result.state,result_status:result.status,deadline:result.deadline,deadline_event_verified:result.reason === 'deadline_exceeded',cancellation_event_absent:!hasCancel}));
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { createDeadline, deadlineState, createCancellationToken, composeAbortSignal, classifyTermination } from '../src/deadline-cancellation.mjs';
|
|
3
|
+
|
|
4
|
+
const now=1_000_000;
|
|
5
|
+
const deadline=createDeadline({timeoutMs:1000,nowMs:now,id:'test-deadline'});
|
|
6
|
+
assert.equal(deadlineState(deadline,{nowMs:now+999}).expired,false);
|
|
7
|
+
assert.equal(deadlineState(deadline,{nowMs:now+1000}).expired,true);
|
|
8
|
+
|
|
9
|
+
const cancellation=createCancellationToken({id:'test-cancel'});
|
|
10
|
+
assert.equal(cancellation.snapshot().requested,false);
|
|
11
|
+
assert.equal(cancellation.request('user_cancelled'),true);
|
|
12
|
+
assert.equal(cancellation.snapshot().requested,true);
|
|
13
|
+
assert.equal(cancellation.request('second'),false);
|
|
14
|
+
|
|
15
|
+
const composed=composeAbortSignal({
|
|
16
|
+
deadline:createDeadline({timeoutMs:60,nowMs:Date.now()}),
|
|
17
|
+
cancellation:createCancellationToken()
|
|
18
|
+
});
|
|
19
|
+
await new Promise(resolve=>setTimeout(resolve,100));
|
|
20
|
+
assert.equal(composed.signal.aborted,true);
|
|
21
|
+
assert.equal(composed.signal.reason,'deadline_exceeded');
|
|
22
|
+
composed.close();
|
|
23
|
+
|
|
24
|
+
const cancellation2=createCancellationToken();
|
|
25
|
+
const composed2=composeAbortSignal({cancellation:cancellation2});
|
|
26
|
+
cancellation2.request('explicit_cancel');
|
|
27
|
+
assert.equal(composed2.signal.aborted,true);
|
|
28
|
+
assert.equal(composed2.signal.reason,'explicit_cancel');
|
|
29
|
+
composed2.close();
|
|
30
|
+
|
|
31
|
+
assert.deepEqual(
|
|
32
|
+
classifyTermination({deadlineExceeded:true,cancellationRequested:false,physicallyTerminated:false,verified:false}),
|
|
33
|
+
{deadline_exceeded:true,cancellation_requested:false,physical_termination:false,verified:false,terminal_semantics:'deadline_exceeded'}
|
|
34
|
+
);
|
|
35
|
+
assert.deepEqual(
|
|
36
|
+
classifyTermination({cancellationRequested:true,physicallyTerminated:true,verified:false}),
|
|
37
|
+
{deadline_exceeded:false,cancellation_requested:true,physical_termination:true,verified:false,terminal_semantics:'termination_unverified'}
|
|
38
|
+
);
|
|
39
|
+
assert.deepEqual(
|
|
40
|
+
classifyTermination({cancellationRequested:true,physicallyTerminated:true,verified:true}),
|
|
41
|
+
{deadline_exceeded:false,cancellation_requested:true,physical_termination:true,verified:true,terminal_semantics:'verified_termination'}
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
console.log(JSON.stringify({
|
|
45
|
+
status:'passed',
|
|
46
|
+
invariant_timeout_not_cancellation:true,
|
|
47
|
+
invariant_cancellation_not_physical_termination:true,
|
|
48
|
+
invariant_physical_termination_not_verified:true
|
|
49
|
+
}));
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { loadProcessPolicy } from '../src/process-policy.mjs';
|
|
5
|
+
import { createProcessRuntime } from '../src/process-runtime.mjs';
|
|
6
|
+
|
|
7
|
+
const state=path.resolve(process.env.DEADBYTE_STATE_ROOT||path.join(os.homedir(),'.deadbyte-mcp'));
|
|
8
|
+
const pointer=JSON.parse(await readFile(path.join(state,'active-release.json'),'utf8'));
|
|
9
|
+
const policy=await loadProcessPolicy(path.join(pointer.generation_root,'process-policy.json'));
|
|
10
|
+
const runtime=createProcessRuntime({
|
|
11
|
+
policy,
|
|
12
|
+
signingKeyPath:path.join(state,'trust','ed25519-private.pem'),
|
|
13
|
+
verifyKeyPath:path.join(state,'trust','ed25519-public.pem')
|
|
14
|
+
});
|
|
15
|
+
const sleep=ms=>new Promise(r=>setTimeout(r,ms));
|
|
16
|
+
const assert=(v,m)=>{if(!v)throw new Error(m);};
|
|
17
|
+
|
|
18
|
+
const timed=await runtime.start({
|
|
19
|
+
profileId:'powershell',rootId:'core',cwd:'.',
|
|
20
|
+
args:['Start-Sleep -Seconds 5'],timeoutMs:500
|
|
21
|
+
});
|
|
22
|
+
await sleep(1000);
|
|
23
|
+
const timedStatus=await runtime.status({sessionId:timed.session_id});
|
|
24
|
+
assert(timedStatus.state==='terminated','deadline did not physically terminate process session');
|
|
25
|
+
assert(timedStatus.termination?.deadline_exceeded===true,'deadline flag missing on process session');
|
|
26
|
+
assert(timedStatus.termination?.physical_termination===true,'deadline termination was not physically observed');
|
|
27
|
+
assert(timedStatus.termination?.verified===true,'deadline termination was not verified');
|
|
28
|
+
assert(timedStatus.termination?.cancellation_requested===false,'deadline was misclassified as cancellation');
|
|
29
|
+
await runtime.verifyEventJournal({sessionId:timed.session_id});
|
|
30
|
+
|
|
31
|
+
const repl=await runtime.start({profileId:'powershell_repl',rootId:'core',cwd:'.',args:[]});
|
|
32
|
+
const before=await runtime.status({sessionId:repl.session_id});
|
|
33
|
+
assert(before.state==='running','REPL did not start');
|
|
34
|
+
await runtime.cancel({sessionId:repl.session_id,reason:'r46_test_cancel'});
|
|
35
|
+
const cancelled=await runtime.status({sessionId:repl.session_id});
|
|
36
|
+
assert(cancelled.state==='terminated','cancellation did not terminate process session');
|
|
37
|
+
assert(cancelled.termination?.cancellation_requested===true,'cancellation flag missing');
|
|
38
|
+
assert(cancelled.termination?.deadline_exceeded===false,'cancellation was misclassified as deadline');
|
|
39
|
+
assert(cancelled.termination?.physical_termination===true,'cancellation did not reach physical termination');
|
|
40
|
+
assert(cancelled.termination?.verified===true,'cancellation termination was not verified');
|
|
41
|
+
await runtime.verifyEventJournal({sessionId:repl.session_id});
|
|
42
|
+
|
|
43
|
+
console.log(JSON.stringify({
|
|
44
|
+
status:'passed',
|
|
45
|
+
invariant_deadline_not_cancellation:timedStatus.termination.cancellation_requested===false,
|
|
46
|
+
invariant_deadline_requires_physical_observation:timedStatus.termination.physical_termination===true,
|
|
47
|
+
invariant_deadline_requires_verified_termination:timedStatus.termination.verified===true,
|
|
48
|
+
invariant_cancellation_distinct:cancelled.termination.deadline_exceeded===false,
|
|
49
|
+
invariant_cancellation_reaches_verified_termination:cancelled.termination.verified===true,
|
|
50
|
+
timed_session:timed.session_id,
|
|
51
|
+
cancelled_session:repl.session_id
|
|
52
|
+
}));
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { readFile, access } from 'node:fs/promises';
|
|
2
|
+
const pid=14932;
|
|
3
|
+
let alive=true; try{process.kill(pid,0)}catch{alive=false}
|
|
4
|
+
let evidence=null; try{const p=process.env.USERPROFILE+'\\.deadbyte-mcp\\evidence\\release'; const {readdir}=await import('node:fs/promises'); evidence=await readdir(p)}catch(e){evidence={error:String(e)}}
|
|
5
|
+
console.log(JSON.stringify({pid,alive,evidence}));
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { createHash, createPublicKey, randomBytes } from 'node:crypto';
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
3
|
+
import { mkdir, readFile, realpath, writeFile } from 'node:fs/promises';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
import { parseActivePointer } from '../src/release-slot.mjs';
|
|
8
|
+
import { loadPowerShellDataFile, runSlotUpdateCli, verifySlotUpdateEnvelope } from '../src/runtime-update.mjs';
|
|
9
|
+
|
|
10
|
+
const HEX64=/^[0-9a-f]{64}$/;
|
|
11
|
+
const RECEIPT_ID=/^r27-[A-Za-z0-9-]+$/;
|
|
12
|
+
const here=path.dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
const packageRoot=path.resolve(here,'..');
|
|
14
|
+
const stateRoot=path.resolve(process.env.DEADBYTE_STATE_ROOT||path.join(os.homedir(),'.deadbyte-mcp'));
|
|
15
|
+
const evidenceRoot=path.join(stateRoot,'evidence','deferred-slot-operations');
|
|
16
|
+
const sha256=bytes=>createHash('sha256').update(bytes).digest('hex');
|
|
17
|
+
const atomicJson=async(file,value)=>{
|
|
18
|
+
await mkdir(path.dirname(file),{recursive:true});
|
|
19
|
+
const temp=`${file}.${process.pid}.${randomBytes(4).toString('hex')}.tmp`;
|
|
20
|
+
await writeFile(temp,`${JSON.stringify(value,null,2)}\n`,{flag:'wx'});
|
|
21
|
+
await import('node:fs/promises').then(fs=>fs.rename(temp,file));
|
|
22
|
+
};
|
|
23
|
+
function activationArgs(manifestSha){
|
|
24
|
+
if(!HEX64.test(manifestSha??'')) throw new Error('activate requires one lowercase manifest SHA-256');
|
|
25
|
+
return ['--stage',packageRoot,'--development-root',packageRoot,'--expected-version','0.15.2',
|
|
26
|
+
'--expected-manifest',manifestSha,'--process-policy',path.join(packageRoot,'controller','deadbyte-process-policy.json'),
|
|
27
|
+
'--process-policy-sha256',null,'--desktop-policy',path.join(packageRoot,'controller','deadbyte-desktop-policy.json'),
|
|
28
|
+
'--desktop-policy-sha256',null,'--semantic-lsp-config',path.join(packageRoot,'controller','deadbyte-semantic-lsp-config.json'),
|
|
29
|
+
'--semantic-lsp-config-sha256',null,'--enable-machine-fs','--enable-observation','--enable-desktop','--enable-autonomous-supervisor'];
|
|
30
|
+
}
|
|
31
|
+
async function bridgeBootstrapArgs(explicitBridgeRoot=null,explicitBridgeSha256=null){
|
|
32
|
+
const hasRoot=typeof explicitBridgeRoot==='string'&&explicitBridgeRoot.length>0;
|
|
33
|
+
const hasSha=typeof explicitBridgeSha256==='string'&&explicitBridgeSha256.length>0;
|
|
34
|
+
if(hasRoot!==hasSha) throw new Error('bridge override requires exact root + SHA pair');
|
|
35
|
+
let bridgeRootRaw,expectedSha256=null;
|
|
36
|
+
if(hasRoot){
|
|
37
|
+
if(!path.isAbsolute(explicitBridgeRoot)||!HEX64.test(explicitBridgeSha256)) throw new Error('bridge override requires exact root + SHA pair');
|
|
38
|
+
bridgeRootRaw=explicitBridgeRoot;expectedSha256=explicitBridgeSha256;
|
|
39
|
+
}else{
|
|
40
|
+
const pointer=parseActivePointer(await readFile(path.join(stateRoot,'active-release.json'),'utf8'));
|
|
41
|
+
const config=await loadPowerShellDataFile(pointer.config_path);
|
|
42
|
+
bridgeRootRaw=config?.Bridge?.WorkingDirectory;
|
|
43
|
+
if(typeof bridgeRootRaw!=='string'||!path.isAbsolute(bridgeRootRaw)) throw new Error('active controller Bridge.WorkingDirectory must be absolute');
|
|
44
|
+
}
|
|
45
|
+
const bridgeRoot=await realpath(path.resolve(bridgeRootRaw));
|
|
46
|
+
const packageFile=path.join(bridgeRoot,'package.json');
|
|
47
|
+
const bridgeScript=path.join(bridgeRoot,'src','bridge.mjs');
|
|
48
|
+
const pkg=JSON.parse(await readFile(packageFile,'utf8'));
|
|
49
|
+
if(pkg?.name!=='deadbyte-chatgpt-bridge') throw new Error('active Bridge package identity mismatch');
|
|
50
|
+
const scriptSha256=sha256(await readFile(bridgeScript));
|
|
51
|
+
if(expectedSha256&&scriptSha256!==expectedSha256) throw new Error('bridge override SHA-256 mismatch');
|
|
52
|
+
return ['--bridge-root',bridgeRoot,'--bridge-script-sha256',scriptSha256];
|
|
53
|
+
}
|
|
54
|
+
async function rollbackArgs(receiptPath){
|
|
55
|
+
const resolved=await realpath(path.resolve(receiptPath));
|
|
56
|
+
const allowed=path.join(stateRoot,'evidence','updates')+path.sep;
|
|
57
|
+
if(!resolved.startsWith(allowed)||path.basename(resolved).toLowerCase()!=='receipt.json'||!RECEIPT_ID.test(path.basename(path.dirname(resolved)))) {
|
|
58
|
+
throw new Error('rollback receipt must be one signed activation receipt under the update evidence store');
|
|
59
|
+
}
|
|
60
|
+
const envelope=JSON.parse(await readFile(resolved,'utf8'));
|
|
61
|
+
const publicKey=createPublicKey(await readFile(path.join(stateRoot,'trust','ed25519-public.pem')));
|
|
62
|
+
if(!verifySlotUpdateEnvelope(envelope,publicKey)||envelope.body?.rollback_performed!==false) throw new Error('rollback activation receipt verification failed');
|
|
63
|
+
const pointer=parseActivePointer(await readFile(path.join(stateRoot,'active-release.json'),'utf8'));
|
|
64
|
+
if(pointer.release_id!==envelope.body?.active_pointer?.release_id||pointer.generation_root!==envelope.body?.active_pointer?.generation_root) {
|
|
65
|
+
throw new Error('rollback receipt does not identify the active release generation');
|
|
66
|
+
}
|
|
67
|
+
return ['--rollback-receipt',resolved];
|
|
68
|
+
}
|
|
69
|
+
async function normalizedArgs(mode,value,{explicitBridgeRoot=null,explicitBridgeSha256=null}={}){
|
|
70
|
+
if(mode==='activate'){
|
|
71
|
+
const args=activationArgs(value);
|
|
72
|
+
args[args.indexOf('--process-policy-sha256')+1]=sha256(await readFile(path.join(packageRoot,'controller','deadbyte-process-policy.json')));
|
|
73
|
+
args[args.indexOf('--desktop-policy-sha256')+1]=sha256(await readFile(path.join(packageRoot,'controller','deadbyte-desktop-policy.json')));
|
|
74
|
+
args[args.indexOf('--semantic-lsp-config-sha256')+1]=sha256(await readFile(path.join(packageRoot,'controller','deadbyte-semantic-lsp-config.json')));
|
|
75
|
+
args.push(...await bridgeBootstrapArgs(explicitBridgeRoot,explicitBridgeSha256));
|
|
76
|
+
return args;
|
|
77
|
+
}
|
|
78
|
+
if(mode==='rollback'){
|
|
79
|
+
if(explicitBridgeRoot||explicitBridgeSha256) throw new Error('rollback does not accept bridge override');
|
|
80
|
+
return rollbackArgs(value);
|
|
81
|
+
}
|
|
82
|
+
throw new Error("operation must be exactly 'activate' or 'rollback'");
|
|
83
|
+
}
|
|
84
|
+
const rawArgs=process.argv.slice(2);
|
|
85
|
+
const mode=rawArgs[0],value=rawArgs[1];
|
|
86
|
+
if(!value||!['activate','rollback'].includes(mode)) throw new Error('usage: deferred-slot-operation-r46.mjs <activate manifest_sha256|rollback activation_receipt> [--bridge-root ABS --bridge-script-sha256 HEX64]');
|
|
87
|
+
let cursor=2,explicitBridgeRoot=null,explicitBridgeSha256=null;
|
|
88
|
+
if(rawArgs[cursor]==='--bridge-root'){
|
|
89
|
+
explicitBridgeRoot=rawArgs[cursor+1]??null;
|
|
90
|
+
if(rawArgs[cursor+2]!=='--bridge-script-sha256') throw new Error('bridge override requires exact root + SHA pair');
|
|
91
|
+
explicitBridgeSha256=rawArgs[cursor+3]??null;cursor+=4;
|
|
92
|
+
}
|
|
93
|
+
let workerFlag=null,operationIdArg=null;
|
|
94
|
+
if(rawArgs[cursor]==='--worker'){workerFlag='--worker';operationIdArg=rawArgs[cursor+1]??null;cursor+=2;}
|
|
95
|
+
if(cursor!==rawArgs.length) throw new Error('deferred slot operation arguments invalid');
|
|
96
|
+
if((explicitBridgeRoot===null)!==(explicitBridgeSha256===null)) throw new Error('bridge override requires exact root + SHA pair');
|
|
97
|
+
if(mode==='rollback'&&(explicitBridgeRoot||explicitBridgeSha256)) throw new Error('rollback does not accept bridge override');
|
|
98
|
+
if(workerFlag==='--worker'){
|
|
99
|
+
if(!/^[0-9a-f]{32}$/.test(operationIdArg??'')) throw new Error('deferred worker operation id invalid');
|
|
100
|
+
const statePath=path.join(evidenceRoot,`${operationIdArg}.json`);
|
|
101
|
+
await new Promise(resolve=>setTimeout(resolve,4000));
|
|
102
|
+
await atomicJson(statePath,{schema:'deadbyte.deferred-slot-operation.v1',operation_id:operationIdArg,mode,state:'running',started_at_utc:new Date().toISOString(),bridge_root:explicitBridgeRoot,bridge_script_sha256:explicitBridgeSha256});
|
|
103
|
+
try{
|
|
104
|
+
const args=await normalizedArgs(mode,value,{explicitBridgeRoot,explicitBridgeSha256});
|
|
105
|
+
const result=await runSlotUpdateCli(args,{stateRoot});
|
|
106
|
+
await atomicJson(statePath,{schema:'deadbyte.deferred-slot-operation.v1',operation_id:operationIdArg,mode,state:'completed',
|
|
107
|
+
completed_at_utc:new Date().toISOString(),receipt_path:result.receipt_path,receipt_sha256:result.receipt_sha256,
|
|
108
|
+
active_release:result.pointer.release_id,generation_root:result.pointer.generation_root,bridge_root:explicitBridgeRoot,bridge_script_sha256:explicitBridgeSha256});
|
|
109
|
+
}catch(error){
|
|
110
|
+
await atomicJson(statePath,{schema:'deadbyte.deferred-slot-operation.v1',operation_id:operationIdArg,mode,state:'failed',
|
|
111
|
+
completed_at_utc:new Date().toISOString(),error:String(error?.message??error),bridge_root:explicitBridgeRoot,bridge_script_sha256:explicitBridgeSha256});
|
|
112
|
+
process.exitCode=1;
|
|
113
|
+
}
|
|
114
|
+
}else{
|
|
115
|
+
const operationId=randomBytes(16).toString('hex');
|
|
116
|
+
const statePath=path.join(evidenceRoot,`${operationId}.json`);
|
|
117
|
+
await atomicJson(statePath,{schema:'deadbyte.deferred-slot-operation.v1',operation_id:operationId,mode,state:'scheduled',scheduled_at_utc:new Date().toISOString(),bridge_root:explicitBridgeRoot,bridge_script_sha256:explicitBridgeSha256});
|
|
118
|
+
const workerArgs=[fileURLToPath(import.meta.url),mode,value];
|
|
119
|
+
if(explicitBridgeRoot) workerArgs.push('--bridge-root',explicitBridgeRoot,'--bridge-script-sha256',explicitBridgeSha256);
|
|
120
|
+
workerArgs.push('--worker',operationId);
|
|
121
|
+
const child=spawn(process.execPath,workerArgs,{
|
|
122
|
+
cwd:packageRoot,detached:true,stdio:'ignore',windowsHide:true,env:{...process.env,DEADBYTE_STATE_ROOT:stateRoot}
|
|
123
|
+
});
|
|
124
|
+
child.unref();
|
|
125
|
+
console.log(JSON.stringify({status:'scheduled',operation_id:operationId,mode,state_path:statePath,execution_provider:'deadbyte_mcp',rdc_operation_count:0,bridge_root:explicitBridgeRoot,bridge_script_sha256:explicitBridgeSha256}));
|
|
126
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { createHash, createPrivateKey, createPublicKey, sign as cryptoSign } from 'node:crypto';
|
|
2
|
+
import { mkdir, open, readFile } from 'node:fs/promises';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
|
|
7
|
+
const DOMAIN=Buffer.from('DEADBYTE-R46-FINAL-CLOSURE-V1\0','utf8');
|
|
8
|
+
const here=path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const packageRoot=path.resolve(here,'..');
|
|
10
|
+
const stateRoot=path.resolve(process.env.DEADBYTE_STATE_ROOT||path.join(os.homedir(),'.deadbyte-mcp'));
|
|
11
|
+
const sha256=bytes=>createHash('sha256').update(bytes).digest('hex');
|
|
12
|
+
const canonical=value=>Array.isArray(value)?value.map(canonical):value&&typeof value==='object'
|
|
13
|
+
?Object.fromEntries(Object.keys(value).sort().map(key=>[key,canonical(value[key])])):value;
|
|
14
|
+
const canonicalJson=value=>JSON.stringify(canonical(value));
|
|
15
|
+
function insist(ok,message){if(!ok)throw new Error(message);}
|
|
16
|
+
async function fileEvidence(file){const bytes=await readFile(file);return {path:path.resolve(file),sha256:sha256(bytes)};}
|
|
17
|
+
|
|
18
|
+
export async function writeFinalClosure({root=packageRoot,runtimeStateRoot=stateRoot,clock=()=>new Date()}={}){
|
|
19
|
+
const candidate=path.resolve(root),state=path.resolve(runtimeStateRoot);
|
|
20
|
+
const manifestBytes=await readFile(path.join(candidate,'MANIFEST.SHA256'));
|
|
21
|
+
const manifestSha256=sha256(manifestBytes);
|
|
22
|
+
const pointer=JSON.parse(await readFile(path.join(state,'active-release.json'),'utf8'));
|
|
23
|
+
insist(pointer.version==='0.15.2'&&pointer.manifest_sha256===manifestSha256,'R46 final closure active candidate mismatch');
|
|
24
|
+
insist(pointer.release_id===`v0.15.2-${manifestSha256.slice(0,16)}`,'R46 final closure release id mismatch');
|
|
25
|
+
|
|
26
|
+
const gatePath=path.join(state,'evidence','release',`r46-windows-gate-${manifestSha256.slice(0,16)}.json`);
|
|
27
|
+
const parityPath=path.join(state,'evidence','release',`r46-parity-${manifestSha256.slice(0,16)}.json`);
|
|
28
|
+
const gate=JSON.parse(await readFile(gatePath,'utf8'));
|
|
29
|
+
const parity=JSON.parse(await readFile(parityPath,'utf8'));
|
|
30
|
+
insist(gate.schema==='deadbyte.r46-windows-gate.v1'&&gate.status==='passed'&&gate.version==='0.15.2','R46 gate evidence invalid');
|
|
31
|
+
insist(gate.manifest_sha256===manifestSha256,'R46 gate evidence candidate mismatch');
|
|
32
|
+
insist(parity.schema==='deadbyte.r46-parity.v1'&&parity.status==='passed'&&parity.version==='0.15.2','R46 parity evidence invalid');
|
|
33
|
+
insist(parity.candidate?.manifest_sha256===manifestSha256&&parity.candidate?.generation_manifest_sha256===pointer.generation_manifest_sha256,'R46 parity active generation mismatch');
|
|
34
|
+
insist(parity.process_policy?.target_repin_verified===true,'R46 target-machine process repin proof missing');
|
|
35
|
+
insist(parity.fileless_powershell?.exit_code===0&&parity.fileless_powershell?.pscommandpath_empty===true&&parity.fileless_powershell?.invocation_path_empty===true&&parity.fileless_powershell?.new_temp_ps1_count===0,'R46 fileless PowerShell proof missing');
|
|
36
|
+
insist(parity.persistent_powershell_repl?.state_persisted===true&&parity.persistent_powershell_repl?.observed_value===42&&parity.persistent_powershell_repl?.wait_for_verified===true&&parity.persistent_powershell_repl?.tail_read_verified===true&&parity.persistent_powershell_repl?.raw_input_not_journaled===true,'R46 persistent PowerShell REPL proof missing');
|
|
37
|
+
insist(parity.retained_output_ring?.eviction_verified===true&&parity.retained_output_ring?.tail_marker_verified===true&&parity.retained_output_ring?.earliest_offset>0,'R46 retained-output ring proof missing');
|
|
38
|
+
|
|
39
|
+
const completed=clock();
|
|
40
|
+
insist(completed instanceof Date&&!Number.isNaN(completed.getTime()),'R46 final closure clock invalid');
|
|
41
|
+
const body={
|
|
42
|
+
schema:'deadbyte.r46-final-closure.v1',
|
|
43
|
+
version:'0.15.2',
|
|
44
|
+
train:'R46',
|
|
45
|
+
release_label:'FILELESS POWERSHELL PRODUCT BASELINE',
|
|
46
|
+
completed_at_utc:completed.toISOString(),
|
|
47
|
+
active_pointer:pointer,
|
|
48
|
+
parent:gate.parent,
|
|
49
|
+
runtime_predecessor:gate.runtime_predecessor,
|
|
50
|
+
features:gate.features,
|
|
51
|
+
process_policy:parity.process_policy,
|
|
52
|
+
fileless_powershell:parity.fileless_powershell,
|
|
53
|
+
persistent_powershell_repl:parity.persistent_powershell_repl,
|
|
54
|
+
retained_output_ring:parity.retained_output_ring,
|
|
55
|
+
evidence:{
|
|
56
|
+
windows_gate:await fileEvidence(gatePath),
|
|
57
|
+
parity:await fileEvidence(parityPath)
|
|
58
|
+
},
|
|
59
|
+
execution_provider:'deadbyte_mcp',
|
|
60
|
+
rdc_operation_count:0
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const privateKey=createPrivateKey(await readFile(path.join(state,'trust','ed25519-private.pem')));
|
|
64
|
+
const publicKey=createPublicKey(privateKey);
|
|
65
|
+
insist(privateKey.asymmetricKeyType==='ed25519'&&publicKey.asymmetricKeyType==='ed25519','R46 closure signing key must be Ed25519');
|
|
66
|
+
const keyId=sha256(Buffer.from(publicKey.export({type:'spki',format:'der'})));
|
|
67
|
+
const bodyBytes=Buffer.from(canonicalJson(body),'utf8');
|
|
68
|
+
const signature=cryptoSign(null,Buffer.concat([DOMAIN,bodyBytes]),privateKey);
|
|
69
|
+
const envelope={
|
|
70
|
+
body,
|
|
71
|
+
body_sha256:sha256(bodyBytes),
|
|
72
|
+
attestation:{
|
|
73
|
+
algorithm:'ed25519',
|
|
74
|
+
domain:'DEADBYTE-R46-FINAL-CLOSURE-V1',
|
|
75
|
+
key_id:keyId,
|
|
76
|
+
signature_base64:signature.toString('base64')
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
envelope.closure_sha256=sha256(Buffer.from(canonicalJson(envelope),'utf8'));
|
|
80
|
+
const bytes=Buffer.from(`${JSON.stringify(envelope,null,2)}\n`,'utf8');
|
|
81
|
+
const dir=path.join(state,'evidence','release');
|
|
82
|
+
await mkdir(dir,{recursive:true});
|
|
83
|
+
const file=path.join(dir,`r46-final-${manifestSha256.slice(0,16)}.json`);
|
|
84
|
+
try{
|
|
85
|
+
const handle=await open(file,'wx',0o600);
|
|
86
|
+
try{await handle.writeFile(bytes);await handle.sync();}finally{await handle.close();}
|
|
87
|
+
return {status:'written',path:file,sha256:sha256(bytes),closure_sha256:envelope.closure_sha256,release_id:pointer.release_id};
|
|
88
|
+
}catch(error){
|
|
89
|
+
if(error?.code!=='EEXIST')throw error;
|
|
90
|
+
const existingBytes=await readFile(file);
|
|
91
|
+
const existing=JSON.parse(existingBytes.toString('utf8').replace(/^\uFEFF/,''));
|
|
92
|
+
insist(existing.body?.version==='0.15.2'&&existing.body?.active_pointer?.manifest_sha256===manifestSha256,'existing R46 closure identity mismatch');
|
|
93
|
+
return {status:'existing',path:file,sha256:sha256(existingBytes),closure_sha256:existing.closure_sha256,release_id:pointer.release_id};
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const invoked=process.argv[1]&&path.resolve(process.argv[1])===fileURLToPath(import.meta.url);
|
|
98
|
+
if(invoked)writeFinalClosure().then(value=>console.log(JSON.stringify(value))).catch(error=>{console.error(error instanceof Error?error.stack:String(error));process.exitCode=1;});
|