deadbyte-mcp 0.15.45 → 0.15.47
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 +52 -27
- package/PROVENANCE.json +260 -257
- package/README.txt +22 -21
- package/RELEASE.json +366 -346
- package/SBOM.cdx.json +2 -2
- package/TEST-MATRIX.json +501 -503
- package/bin/deadbyte-kernel-lock-r74.exe +0 -0
- package/bridge/package.json +1 -1
- package/controller/deadbyte-controller.ps1 +20 -6
- package/controller/deadbyte-process-policy.json +7 -7
- package/docs/R73-CAPABILITY-GAP-MATRIX.json +17 -0
- package/docs/R73-EXECUTION-CONTRACT.md +11 -0
- package/docs/R73-IMPLEMENTATION-CHANGELOG.md +12 -0
- package/docs/R73-REMAINING-DEFECTS.md +9 -0
- package/docs/R74-KERNEL-LOCK-BINARY.json +11 -0
- package/native/deadbyte-kernel-lock-r74.cpp +65 -0
- package/package.json +2 -2
- package/scripts/build-kernel-lock-r74.ps1 +28 -0
- package/scripts/gate-windows-r72.ps1 +76 -0
- package/scripts/npm-prepublish-contract-r74.mjs +113 -0
- package/scripts/npm-release-r48.mjs +4 -0
- package/scripts/npm-release-readiness-r72.mjs +76 -0
- package/scripts/npm-release-readiness-r74.mjs +46 -0
- package/scripts/r73-atomic-benchmark.mjs +39 -0
- package/src/atomic-mutation.mjs +99 -9
- package/src/deadbyte-cli.mjs +722 -721
- package/src/kernel-lock-r74.mjs +125 -0
- package/src/launcher-isolation.mjs +58 -0
- package/src/remote-authority.mjs +75 -75
- package/src/remote-mcp-machine-smoke-client.mjs +84 -84
- package/src/runtime-update.mjs +2056 -2008
- package/src/staging-input-isolation.mjs +79 -0
- package/src/version.mjs +1 -1
- package/test/atomic-mutation.test.mjs +68 -2
- package/test/commander-schema-differential-r48.test.mjs +1 -1
- package/test/deferred-slot-operation-behavior.test.mjs +2 -2
- package/test/r58-cleanup-retention.test.mjs +3 -3
- package/test/r58-concurrent-recovery.test.mjs +3 -3
- package/test/r58-stale-owner.test.mjs +1 -1
- package/test/r59-tool-surface-compat.test.mjs +2 -2
- package/test/r60-authority-coherence.test.mjs +10 -2
- package/test/r71-isolated-slot-rollback-restore.test.mjs +11 -11
- package/test/r71-release-identity.test.mjs +12 -10
- package/test/r72-launcher-isolation.test.mjs +99 -0
- package/test/r72-publication-fence.test.mjs +56 -0
- package/test/r72-publication-output-integrity.test.mjs +110 -0
- package/test/r72-remote-safe-contract.test.mjs +101 -0
- package/test/r72-staging-input-isolation.test.mjs +87 -0
- package/test/r72-staging-workspace-isolation.test.mjs +114 -0
- package/test/r73-package-boundary.test.mjs +22 -0
- package/test/r74-kernel-lock.test.mjs +150 -0
- package/test/r74-prepublish-contract.test.mjs +89 -0
- package/test/remote-session-cli.test.mjs +110 -64
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import {createHash} from 'node:crypto';
|
|
2
|
+
import {mkdtemp,readFile,rm,writeFile} from 'node:fs/promises';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import {performance} from 'node:perf_hooks';
|
|
6
|
+
import {pathToFileURL} from 'node:url';
|
|
7
|
+
|
|
8
|
+
const baseline=process.argv[2];
|
|
9
|
+
if(!baseline||!path.isAbsolute(baseline))throw new Error('absolute verified predecessor source required');
|
|
10
|
+
const candidate=path.resolve('src/atomic-mutation.mjs');
|
|
11
|
+
const sha=bytes=>createHash('sha256').update(bytes).digest('hex');
|
|
12
|
+
const modules={baseline:(await import(pathToFileURL(baseline))).atomicMutation,
|
|
13
|
+
candidate:(await import(pathToFileURL(candidate))).atomicMutation};
|
|
14
|
+
const rounds=[];
|
|
15
|
+
for(const order of [['baseline','candidate'],['candidate','baseline'],['baseline','candidate'],['candidate','baseline']]){
|
|
16
|
+
for(const label of order){
|
|
17
|
+
const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r73-bench-'));
|
|
18
|
+
const target=path.join(root,'value.bin');
|
|
19
|
+
const initial=Buffer.from('before');await writeFile(target,initial);
|
|
20
|
+
const readState=async file=>{const bytes=await readFile(file);return {exists:true,sha256:sha(bytes)};};
|
|
21
|
+
let prior=sha(initial);
|
|
22
|
+
const times=[];
|
|
23
|
+
try{
|
|
24
|
+
for(let i=0;i<110;i++){
|
|
25
|
+
const next=Buffer.from(`bounded replacement ${i}`);
|
|
26
|
+
const start=performance.now();
|
|
27
|
+
await modules[label]({target,bytes:next,expected:prior,readState,
|
|
28
|
+
assertExpected(state,expected){if(state.sha256!==expected)throw new Error('benchmark identity drift');}});
|
|
29
|
+
const elapsed=performance.now()-start;
|
|
30
|
+
if(i>=10)times.push(elapsed);
|
|
31
|
+
prior=sha(next);
|
|
32
|
+
}
|
|
33
|
+
times.sort((a,b)=>a-b);
|
|
34
|
+
const at=q=>Number(times[Math.ceil(q*times.length)-1].toFixed(4));
|
|
35
|
+
rounds.push({label,n:times.length,p50_ms:at(.5),p95_ms:at(.95),max_ms:at(1)});
|
|
36
|
+
}finally{await rm(root,{recursive:true,force:true});}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
console.log(JSON.stringify({schema:'deadbyte.r73-atomic-benchmark.v1',workload:'exact-precondition fsync+rename on isolated Windows scratch',rounds}));
|
package/src/atomic-mutation.mjs
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { createHash, randomBytes } from 'node:crypto';
|
|
2
|
-
import { open, rename, rm, stat } from 'node:fs/promises';
|
|
2
|
+
import { open, readFile, rename, rm, stat, writeFile } from 'node:fs/promises';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
+
import {performance} from 'node:perf_hooks';
|
|
5
|
+
import {acquireKernelExclusiveLock, verifyKernelLockBinary} from './kernel-lock-r74.mjs';
|
|
4
6
|
|
|
5
7
|
const sha256Bytes = bytes => createHash('sha256').update(bytes).digest('hex');
|
|
6
8
|
const LOCK_WAIT_MS = 10;
|
|
@@ -9,20 +11,90 @@ const LOCK_STALE_MS = 120000;
|
|
|
9
11
|
|
|
10
12
|
async function acquireMutationLock(target) {
|
|
11
13
|
const lock = `${target}.deadbyte-mutation.lock`;
|
|
12
|
-
const
|
|
14
|
+
const kernelLock = `${target}.deadbyte-mutation.lock.kernel`;
|
|
15
|
+
const ownership = `${process.pid}:${Date.now()}:${randomBytes(16).toString('hex')}`;
|
|
16
|
+
const verified = await verifyKernelLockBinary();
|
|
17
|
+
|
|
18
|
+
if (verified.ok && process.env.DEADBYTE_R74_KERNEL_LOCK !== '0') {
|
|
19
|
+
try {
|
|
20
|
+
const existing = await readFile(lock, 'utf8').catch(() => null);
|
|
21
|
+
if (existing && existing.includes('abandoned')) {
|
|
22
|
+
throw new Error('atomic mutation lock abandoned; reconciliation required');
|
|
23
|
+
}
|
|
24
|
+
} catch (err) {
|
|
25
|
+
if (err.message.includes('abandoned')) throw err;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const releaseKernel = await acquireKernelExclusiveLock(kernelLock, {
|
|
29
|
+
timeoutMs: LOCK_TIMEOUT_MS,
|
|
30
|
+
pollMs: LOCK_WAIT_MS
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
await writeFile(lock, ownership);
|
|
35
|
+
} catch (err) {
|
|
36
|
+
await releaseKernel().catch(() => {});
|
|
37
|
+
throw err;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return async () => {
|
|
41
|
+
let ownershipLost = false;
|
|
42
|
+
try {
|
|
43
|
+
const onDisk = await readFile(lock, 'utf8').catch(() => null);
|
|
44
|
+
if (onDisk !== ownership) {
|
|
45
|
+
ownershipLost = true;
|
|
46
|
+
}
|
|
47
|
+
} finally {
|
|
48
|
+
await releaseKernel().catch(() => {});
|
|
49
|
+
await rm(kernelLock, {force: true}).catch(() => {});
|
|
50
|
+
if (!ownershipLost) {
|
|
51
|
+
await rm(lock, {force: true}).catch(() => {});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (ownershipLost) {
|
|
55
|
+
throw new Error('atomic mutation lock ownership lost');
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const started = performance.now();
|
|
13
61
|
while (true) {
|
|
14
62
|
try {
|
|
15
63
|
const handle = await open(lock, 'wx', 0o600);
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
64
|
+
try {
|
|
65
|
+
await handle.writeFile(ownership);
|
|
66
|
+
await handle.sync();
|
|
67
|
+
await handle.close();
|
|
68
|
+
} catch (error) {
|
|
69
|
+
await handle.close().catch(() => {});
|
|
70
|
+
try { await rm(lock, {force:true}); }
|
|
71
|
+
catch { error.lock_cleanup_failed = true; }
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
return async () => {
|
|
75
|
+
if (await readFile(lock, 'utf8').catch(() => null) !== ownership)
|
|
76
|
+
throw new Error('atomic mutation lock ownership lost');
|
|
77
|
+
await rm(lock, { force: true });
|
|
78
|
+
};
|
|
20
79
|
} catch (error) {
|
|
21
80
|
if (error?.code !== 'EEXIST') throw error;
|
|
22
|
-
if (
|
|
81
|
+
if (performance.now() - started >= LOCK_TIMEOUT_MS) throw new Error('atomic mutation lock timed out');
|
|
23
82
|
try {
|
|
24
83
|
const info = await stat(lock);
|
|
25
|
-
if (Date.now() - info.mtimeMs > LOCK_STALE_MS)
|
|
84
|
+
if (Date.now() - info.mtimeMs > LOCK_STALE_MS) {
|
|
85
|
+
const contents = await readFile(lock, 'utf8').catch(() => null);
|
|
86
|
+
const owner = Number(/^\d+/.exec(contents??'')?.[0]);
|
|
87
|
+
let ownerAlive = true;
|
|
88
|
+
if (Number.isSafeInteger(owner) && owner > 0) {
|
|
89
|
+
try { process.kill(owner, 0); }
|
|
90
|
+
catch (error) { ownerAlive = error?.code !== 'ESRCH'; }
|
|
91
|
+
}
|
|
92
|
+
if (!ownerAlive) {
|
|
93
|
+
// A check-then-unlink race can remove a *new* writer's lock. Without
|
|
94
|
+
// an atomic compare-and-delete primitive, preserve evidence and stop.
|
|
95
|
+
throw new Error('atomic mutation lock abandoned; reconciliation required');
|
|
96
|
+
}
|
|
97
|
+
}
|
|
26
98
|
} catch (statError) {
|
|
27
99
|
if (statError?.code !== 'ENOENT') throw statError;
|
|
28
100
|
}
|
|
@@ -47,6 +119,8 @@ export async function atomicMutation({
|
|
|
47
119
|
throw new Error('atomic mutation requires readState and assertExpected');
|
|
48
120
|
}
|
|
49
121
|
const releaseLock = await acquireMutationLock(target);
|
|
122
|
+
let committed = false;
|
|
123
|
+
let operationFailure = null;
|
|
50
124
|
try {
|
|
51
125
|
const before = await readState(target);
|
|
52
126
|
assertExpected(before, expected);
|
|
@@ -68,6 +142,7 @@ export async function atomicMutation({
|
|
|
68
142
|
if (authorityCheck) await authorityCheck();
|
|
69
143
|
|
|
70
144
|
await rename(temp, target);
|
|
145
|
+
committed = true;
|
|
71
146
|
} catch (error) {
|
|
72
147
|
if (handle) await handle.close().catch(() => {});
|
|
73
148
|
await rm(temp, { force: true }).catch(() => {});
|
|
@@ -87,7 +162,22 @@ export async function atomicMutation({
|
|
|
87
162
|
failure.expected_after_sha256 = expectedAfter;
|
|
88
163
|
throw failure;
|
|
89
164
|
}
|
|
165
|
+
} catch (error) {
|
|
166
|
+
operationFailure = error;
|
|
167
|
+
throw error;
|
|
90
168
|
} finally {
|
|
91
|
-
await releaseLock();
|
|
169
|
+
try { await releaseLock(); }
|
|
170
|
+
catch (error) {
|
|
171
|
+
if (operationFailure) operationFailure.lock_cleanup_failed = true;
|
|
172
|
+
else {
|
|
173
|
+
error.lock_cleanup_failed = true;
|
|
174
|
+
if (committed) {
|
|
175
|
+
error.outcome = 'FAILED_AFTER_UNKNOWN_EFFECT';
|
|
176
|
+
error.side_effect_state = 'unknown';
|
|
177
|
+
error.expected_after_sha256 = sha256Bytes(bytes);
|
|
178
|
+
}
|
|
179
|
+
throw error;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
92
182
|
}
|
|
93
183
|
}
|