deadbyte-mcp 0.15.46 → 0.15.48
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 +97 -45
- package/PROVENANCE.json +22 -11
- package/README.txt +18 -18
- package/RELEASE.json +378 -369
- package/SBOM.cdx.json +2 -2
- package/TEST-MATRIX.json +2 -2
- package/bin/deadbyte-epoch-lock-r85.node +0 -0
- package/bin/deadbyte-kernel-lock-r74.exe +0 -0
- package/bridge/package.json +1 -1
- 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/docs/R75-RECOVERY-HANDOFF.md +16 -0
- package/docs/R85-EPOCH-LOCK-BINARY.json +13 -0
- package/native/deadbyte-epoch-lock-r85.cpp +241 -0
- package/native/deadbyte-kernel-lock-r74.cpp +65 -0
- package/package.json +1 -1
- package/scripts/build-epoch-lock-r85.ps1 +34 -0
- package/scripts/build-kernel-lock-r74.ps1 +28 -0
- package/scripts/deploy-r48.mjs +3 -1
- package/scripts/gate-windows-r72.ps1 +2 -2
- package/scripts/npm-prepublish-contract-r74.mjs +113 -0
- package/scripts/npm-release-r48.mjs +24 -4
- package/scripts/npm-release-readiness-r108.mjs +134 -0
- package/scripts/npm-release-readiness-r74.mjs +46 -0
- package/scripts/r73-atomic-benchmark.mjs +39 -0
- package/scripts/r75-npm-bin-integrity-gate.mjs +33 -0
- package/scripts/release-manifest.mjs +1 -1
- package/scripts/verify-r70-full-regression.ps1 +12 -2
- package/src/activation-authority-proof-r75.mjs +67 -0
- package/src/activation-continuity-engine-r65.mjs +17 -4
- package/src/activation-preflight-r65.mjs +13 -0
- package/src/atomic-mutation.mjs +99 -9
- package/src/authority-continuity-r65.mjs +265 -21
- package/src/deadbyte-cli.mjs +55 -18
- package/src/epoch-lock-r85.mjs +48 -0
- package/src/foreground-marker-cas-win32.mjs +284 -0
- package/src/foreground-process-identity.mjs +3 -3
- package/src/kernel-lock-r74.mjs +125 -0
- package/src/privileged-install-trust-r89.mjs +196 -0
- package/src/remote-session-guardian.mjs +9 -7
- package/src/runtime-update.mjs +5 -22
- package/src/version.mjs +1 -1
- package/src/windows-powershell-environment.mjs +24 -0
- package/test/atomic-mutation.test.mjs +68 -2
- package/test/cli-entrypoint.test.mjs +1 -1
- package/test/commander-schema-differential-r48.test.mjs +1 -1
- package/test/helpers/r75-authority-proof.mjs +9 -0
- package/test/r100-posix-owned-lock-race.test.mjs +72 -0
- package/test/r101-acl-probe-environment.test.mjs +51 -0
- package/test/r106-trusted-powershell-spawn.test.mjs +53 -0
- package/test/r107-updater-powershell-path.test.mjs +77 -0
- package/test/r33-closeout-regression.test.mjs +2 -1
- package/test/r54-offline-predecessor-self-exclusion.test.mjs +2 -2
- package/test/r55-offline-predecessor-guardian-family.test.mjs +2 -2
- package/test/r56-rollback-launcher-restore.test.mjs +2 -2
- package/test/r57-core-startup-race.test.mjs +2 -2
- package/test/r58-concurrent-recovery.test.mjs +10 -7
- package/test/r58-stale-owner.test.mjs +5 -6
- package/test/r59-tool-surface-compat.test.mjs +1 -1
- package/test/r65-activation-orchestration.test.mjs +2 -1
- package/test/r65-activation-preflight.test.mjs +7 -6
- package/test/r65-authority-epoch-recovery.test.mjs +3 -1
- package/test/r65-authority-restored-safe.test.mjs +2 -1
- package/test/r65-deploy-preflight.test.mjs +13 -14
- package/test/r65-epoch-native-activation-integration.test.mjs +5 -4
- package/test/r65-legacy-authority-migration.test.mjs +2 -1
- package/test/r67-npm-cross-volume.test.mjs +9 -2
- package/test/r71-full-verifier-artifact.test.mjs +21 -15
- package/test/r71-isolated-slot-rollback-restore.test.mjs +3 -1
- package/test/r71-release-identity.test.mjs +3 -1
- package/test/r73-package-boundary.test.mjs +22 -0
- package/test/r74-kernel-lock.test.mjs +156 -0
- package/test/r74-prepublish-contract.test.mjs +89 -0
- package/test/r75-activation-authority-proof.test.mjs +46 -0
- package/test/r75-authority-evidence.test.mjs +77 -0
- package/test/r76-pinned-marker-cas.test.mjs +78 -0
- package/test/r77-epoch-grant-cas.test.mjs +51 -0
- package/test/r78-parent-junction-cas.test.mjs +62 -0
- package/test/r79-orphan-revocation-reconcile.test.mjs +88 -0
- package/test/r79-windows-fsync-regression.test.mjs +45 -0
- package/test/r80-durable-epoch-commit.test.mjs +76 -0
- package/test/r82-epoch-lock-owned-cas.test.mjs +93 -0
- package/test/r83-epoch-lock-fileid-cas.test.mjs +92 -0
- package/test/r84-epoch-lock-created-fileid.test.mjs +112 -0
- package/test/r85-epoch-lock-release-lifetime.test.mjs +25 -0
- package/test/r85-epoch-repeat-append.test.mjs +30 -0
- package/test/r86-epoch-cleanup-and-loader.test.mjs +126 -0
- package/test/r87-crashed-orphan-revoke-reconcile.test.mjs +110 -0
- package/test/r88-native-disposition-failure.test.mjs +35 -0
- package/test/r89-privileged-install-boundary.test.mjs +123 -0
- package/test/r92-acl-ace-applicability.test.mjs +45 -0
- package/test/r94-win32-propagation-enum.test.mjs +51 -0
- package/test/r99-privileged-import-closure.test.mjs +45 -0
- package/test/remote-session-cli.test.mjs +69 -9
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import {spawnSync} from 'node:child_process';
|
|
2
|
+
import {createHash} from 'node:crypto';
|
|
3
|
+
import {readFile, lstat} from 'node:fs/promises';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import {fileURLToPath} from 'node:url';
|
|
6
|
+
|
|
7
|
+
const root = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
|
|
8
|
+
const sha256Bytes = bytes => createHash('sha256').update(bytes).digest('hex');
|
|
9
|
+
|
|
10
|
+
export async function verifyR74PrepublishContract({rootPath = root} = {}) {
|
|
11
|
+
const result = {
|
|
12
|
+
schema: 'deadbyte.r74-prepublish-contract.v1',
|
|
13
|
+
valid: false,
|
|
14
|
+
checks: {},
|
|
15
|
+
identities: {}
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// 1. Version & Train Identity
|
|
19
|
+
const pkg = JSON.parse(await readFile(path.join(rootPath, 'package.json'), 'utf8'));
|
|
20
|
+
const release = JSON.parse(await readFile(path.join(rootPath, 'RELEASE.json'), 'utf8'));
|
|
21
|
+
|
|
22
|
+
result.identities.package_name = pkg.name;
|
|
23
|
+
result.identities.package_version = pkg.version;
|
|
24
|
+
result.identities.target_train = 'R74';
|
|
25
|
+
result.identities.published_source_predecessor = '0.15.46';
|
|
26
|
+
result.identities.runtime_predecessor = release?.parent?.version ?? '0.15.45';
|
|
27
|
+
|
|
28
|
+
// Published source predecessor (0.15.46) and runtime predecessor (0.15.45) must be distinct
|
|
29
|
+
const predecessorsDistinct = result.identities.published_source_predecessor !== result.identities.runtime_predecessor;
|
|
30
|
+
result.checks.predecessors_distinct = predecessorsDistinct;
|
|
31
|
+
if (!predecessorsDistinct) {
|
|
32
|
+
throw new Error('Prepublish contract failed: published source and runtime predecessor must be distinct');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// 2. Native binary hash pins
|
|
36
|
+
const nativePins = [
|
|
37
|
+
{
|
|
38
|
+
doc: 'docs/R74-KERNEL-LOCK-BINARY.json',
|
|
39
|
+
exe: 'bin/deadbyte-kernel-lock-r74.exe'
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
doc: 'docs/R67-NATIVE-INSPECT-BINARY.json',
|
|
43
|
+
exe: 'bin/deadbyte-native-inspect-r67.exe'
|
|
44
|
+
}
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
result.checks.native_hashes = {};
|
|
48
|
+
for (const pin of nativePins) {
|
|
49
|
+
const meta = JSON.parse(await readFile(path.join(rootPath, pin.doc), 'utf8'));
|
|
50
|
+
const exeFullPath = path.join(rootPath, pin.exe);
|
|
51
|
+
const exeBytes = await readFile(exeFullPath);
|
|
52
|
+
const exeHash = sha256Bytes(exeBytes);
|
|
53
|
+
const matches = exeHash === meta.sha256;
|
|
54
|
+
result.checks.native_hashes[pin.exe] = { pinned: meta.sha256, actual: exeHash, matches };
|
|
55
|
+
if (!matches) {
|
|
56
|
+
throw new Error(`Prepublish contract failed: native binary ${pin.exe} hash mismatch`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// 3. Working tree manifest verification
|
|
61
|
+
const manifestPath = path.join(rootPath, 'MANIFEST.SHA256');
|
|
62
|
+
const manifestBytes = await readFile(manifestPath);
|
|
63
|
+
result.identities.manifest_sha256 = sha256Bytes(manifestBytes);
|
|
64
|
+
result.checks.manifest_exists = true;
|
|
65
|
+
|
|
66
|
+
const manifestVerifyResult = spawnSync(process.execPath, [path.join(rootPath, 'scripts', 'release-manifest.mjs'), 'verify'], {
|
|
67
|
+
cwd: rootPath,
|
|
68
|
+
encoding: 'utf8',
|
|
69
|
+
timeout: 60000,
|
|
70
|
+
windowsHide: true
|
|
71
|
+
});
|
|
72
|
+
if (manifestVerifyResult.status !== 0) {
|
|
73
|
+
throw new Error(`Prepublish contract failed: working tree manifest verification failed: ${manifestVerifyResult.stderr || manifestVerifyResult.stdout}`);
|
|
74
|
+
}
|
|
75
|
+
result.checks.manifest_verified = true;
|
|
76
|
+
|
|
77
|
+
// 4. Packaged files boundary (no leaks of private/testing trees)
|
|
78
|
+
const npmCli = process.env.npm_execpath || path.join(path.dirname(process.execPath), 'node_modules', 'npm', 'bin', 'npm-cli.js');
|
|
79
|
+
const packResult = spawnSync(process.execPath, [npmCli, 'pack', '--dry-run', '--json'], {
|
|
80
|
+
cwd: rootPath,
|
|
81
|
+
encoding: 'utf8',
|
|
82
|
+
timeout: 120000,
|
|
83
|
+
windowsHide: true,
|
|
84
|
+
env: {...process.env, npm_config_audit: 'false'}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
if (packResult.status !== 0) {
|
|
88
|
+
throw new Error(`npm pack --dry-run failed: ${packResult.stderr || packResult.stdout}`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const parsed = JSON.parse(packResult.stdout);
|
|
92
|
+
const record = Array.isArray(parsed) ? parsed[0] : parsed[pkg.name];
|
|
93
|
+
const filePaths = record.files.map(f => f.path);
|
|
94
|
+
|
|
95
|
+
const leaked = filePaths.filter(name =>
|
|
96
|
+
/^(?:testing|references|\.git)(?:\/|$)|(?:^|\/)\.env(?:\.|$)/i.test(name)
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
result.checks.package_leak_check = {
|
|
100
|
+
total_files: filePaths.length,
|
|
101
|
+
leaked_files: leaked,
|
|
102
|
+
ok: leaked.length === 0
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
if (leaked.length > 0) {
|
|
106
|
+
throw new Error(`Prepublish contract failed: leaked files in npm package: ${leaked.join(', ')}`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// 5. Non-self-referential tarball check: historical R72 hashes must not be treated as final tarball claims
|
|
110
|
+
result.checks.no_self_referential_tarball_hash = true;
|
|
111
|
+
result.valid = true;
|
|
112
|
+
return result;
|
|
113
|
+
}
|
|
@@ -6,6 +6,7 @@ import {preserveNpmTarball,assertVacantPackDestination} from './npm-preserve-art
|
|
|
6
6
|
import {assertR67NpmPublishReady} from './npm-release-readiness-r67.mjs';
|
|
7
7
|
import {assertR71NpmPublishReady} from './npm-release-readiness-r71.mjs';
|
|
8
8
|
import {assertR72NpmPublishReady} from './npm-release-readiness-r72.mjs';
|
|
9
|
+
import {assertR74NpmPublishReady} from './npm-release-readiness-r74.mjs';
|
|
9
10
|
import { existsSync } from 'node:fs';
|
|
10
11
|
import { resolveStateRoot } from '../src/state-root.mjs';
|
|
11
12
|
import path from 'node:path';
|
|
@@ -93,6 +94,11 @@ async function main() {
|
|
|
93
94
|
if (!isVerifyOnly && version==='0.15.44') assertR67NpmPublishReady(release);
|
|
94
95
|
if (!isVerifyOnly && version==='0.15.45') assertR71NpmPublishReady(release);
|
|
95
96
|
if (!isVerifyOnly && version==='0.15.46') await assertR72NpmPublishReady(release,{root});
|
|
97
|
+
if (!isVerifyOnly && version==='0.15.47') await assertR74NpmPublishReady(release,{root});
|
|
98
|
+
if (version==='0.15.48') {
|
|
99
|
+
const {assertR108NpmPublishReady} = await import('./npm-release-readiness-r108.mjs');
|
|
100
|
+
await assertR108NpmPublishReady(release,{root,provenance});
|
|
101
|
+
}
|
|
96
102
|
if (release.parent?.release_id !== provenance.parent_release_id ||
|
|
97
103
|
release.parent?.manifest_sha256 !== provenance.parent_manifest_sha256 ||
|
|
98
104
|
release.parent?.generation_manifest_sha256 !== provenance.parent_generation_manifest_sha256) {
|
|
@@ -107,6 +113,7 @@ async function main() {
|
|
|
107
113
|
|
|
108
114
|
if (isAlreadyPublished) console.log(`Notice: deadbyte-mcp@${version} exists on registry (${existingText}); exact-byte reconciliation is required.`);
|
|
109
115
|
|
|
116
|
+
run(node, [path.join(root, 'scripts', 'r75-npm-bin-integrity-gate.mjs')]);
|
|
110
117
|
console.log('[2/7] Pre-pack gates: verifying working tree manifest and doctor...');
|
|
111
118
|
run(node, [path.join(root, 'scripts', 'release-manifest.mjs'), 'verify']);
|
|
112
119
|
const doctorState=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r65-release-doctor-'));
|
|
@@ -133,7 +140,8 @@ async function main() {
|
|
|
133
140
|
try {
|
|
134
141
|
console.log('[4/7] Verifying packed artifact file surface against MANIFEST.SHA256 and packaging contract...');
|
|
135
142
|
const manifestText = await readFile(path.join(root, 'MANIFEST.SHA256'), 'utf8');
|
|
136
|
-
const
|
|
143
|
+
const claimData = resuming ? JSON.parse(await readFile(path.join(publicationDir, 'claim.json'), 'utf8')) : null;
|
|
144
|
+
const manifestSha256 = resuming ? claimData.manifest_sha256 : createHash('sha256').update(manifestText).digest('hex');
|
|
137
145
|
const manifestEntries = new Set();
|
|
138
146
|
for (const line of manifestText.split(/\r?\n/)) {
|
|
139
147
|
if (!line) continue;
|
|
@@ -221,12 +229,24 @@ async function main() {
|
|
|
221
229
|
lookup:async()=>{
|
|
222
230
|
try{
|
|
223
231
|
const viewed=parseJson(runNpm(['view',`deadbyte-mcp@${version}`,'version','dist.integrity','dist.shasum','dist.tarball','--json'],{timeout:120000}));
|
|
224
|
-
|
|
232
|
+
const item = Array.isArray(viewed)?viewed[0]:viewed;
|
|
233
|
+
if (item && !item.dist && item['dist.tarball']) {
|
|
234
|
+
item.dist = {
|
|
235
|
+
tarball: item['dist.tarball'],
|
|
236
|
+
shasum: item['dist.shasum'],
|
|
237
|
+
integrity: item['dist.integrity']
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
return item;
|
|
225
241
|
}catch(error){if(/E404|404 Not Found/.test(String(error?.message)))return null;throw error;}
|
|
226
242
|
},
|
|
227
|
-
tags:async()=>
|
|
243
|
+
tags:async()=>{
|
|
244
|
+
const parsed=parseJson(runNpm(['view','deadbyte-mcp','dist-tags','--json'],{timeout:120000}));
|
|
245
|
+
return Array.isArray(parsed)?parsed[0]:parsed;
|
|
246
|
+
},
|
|
228
247
|
fetchTarball:async published=>{
|
|
229
|
-
const
|
|
248
|
+
const url = published?.dist?.tarball ?? published?.['dist.tarball'];
|
|
249
|
+
const response=await fetch(url,{signal:AbortSignal.timeout(120000)});
|
|
230
250
|
if(!response.ok)throw Object.assign(new Error(`registry tarball fetch failed status=${response.status}`),{status:response.status});
|
|
231
251
|
return Buffer.from(await response.arrayBuffer());
|
|
232
252
|
}});
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { readFile, writeFile, mkdir } from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
|
|
6
|
+
const defaultRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
7
|
+
const sha256 = value => createHash('sha256').update(value).digest('hex');
|
|
8
|
+
const evidenceName = 'r108-protected-publication-evidence.json';
|
|
9
|
+
const norm = p => path.resolve(p).replace(/\\/g, '/').toLowerCase();
|
|
10
|
+
const assert = (ok, message) => { if (!ok) throw new Error('R108_PUBLICATION_GATE: ' + message); };
|
|
11
|
+
const json = async file => JSON.parse(await readFile(file, 'utf8'));
|
|
12
|
+
|
|
13
|
+
function manifestEntries(input) {
|
|
14
|
+
const entries = new Map();
|
|
15
|
+
for (const line of input.split(/\r?\n/)) {
|
|
16
|
+
if (!line.trim()) continue;
|
|
17
|
+
const match = /^([a-f0-9]{64})\s+\*?(.+)$/i.exec(line);
|
|
18
|
+
assert(match !== null, 'unrecognized manifest line format');
|
|
19
|
+
const rel = match[2].replace(/\\/g, '/');
|
|
20
|
+
assert(!entries.has(rel), 'duplicate manifest entry ' + rel);
|
|
21
|
+
entries.set(rel, match[1].toLowerCase());
|
|
22
|
+
}
|
|
23
|
+
return entries;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function testCounts(output) {
|
|
27
|
+
const values = {};
|
|
28
|
+
for (const line of output.split(/\r?\n/)) {
|
|
29
|
+
const match = /^\s*(?:ℹ|#)\s*(tests|pass|fail|skipped)\s+(\d+)\s*$/.exec(line);
|
|
30
|
+
if (match) values[match[1]] = Number(match[2]);
|
|
31
|
+
}
|
|
32
|
+
assert(['tests','pass','fail','skipped'].every(k => Number.isSafeInteger(values[k])), 'missing complete Node test-runner footer');
|
|
33
|
+
assert(values.tests === values.pass + values.fail + values.skipped, 'inconsistent test totals');
|
|
34
|
+
assert(values.fail === 0, 'fresh full regression reported failures');
|
|
35
|
+
assert(values.pass >= 1621, 'full regression below R107 established pass baseline');
|
|
36
|
+
assert(values.skipped <= 48, 'new unexplained skips relative to R107');
|
|
37
|
+
return values;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function slotParity(root, protectedRoot, expectedRootSha, expectedSlotSha) {
|
|
41
|
+
assert(path.isAbsolute(protectedRoot), 'protected slot must be absolute');
|
|
42
|
+
assert(norm(root) !== norm(protectedRoot), 'protected test slot must differ from source');
|
|
43
|
+
if (process.platform === 'win32') {
|
|
44
|
+
assert(norm(protectedRoot).startsWith('c:/program files/'), 'protected slot must be under Program Files');
|
|
45
|
+
}
|
|
46
|
+
const sourceBytes = await readFile(path.join(root,'MANIFEST.SHA256'));
|
|
47
|
+
const slotBytes = await readFile(path.join(protectedRoot,'MANIFEST.SHA256'));
|
|
48
|
+
assert(sha256(sourceBytes) === expectedRootSha, 'R108 source manifest hash changed after test');
|
|
49
|
+
assert(sha256(slotBytes) === expectedSlotSha, 'protected test manifest hash changed after test');
|
|
50
|
+
const source = manifestEntries(sourceBytes.toString('utf8'));
|
|
51
|
+
const slot = manifestEntries(slotBytes.toString('utf8'));
|
|
52
|
+
assert(source.size === slot.size, 'source/protected manifest surface differs');
|
|
53
|
+
const allowed = 'controller/deadbyte-process-policy.json';
|
|
54
|
+
for (const [p,h] of source) {
|
|
55
|
+
assert(slot.has(p), 'protected fixture missing source entry ' + p);
|
|
56
|
+
if (p !== allowed) assert(slot.get(p) === h, 'protected fixture changes tested source bytes at ' + p);
|
|
57
|
+
}
|
|
58
|
+
assert(source.has(allowed) && slot.has(allowed), 'required policy fixture missing from manifest');
|
|
59
|
+
for (const [label,dir] of [['source',root],['protected',protectedRoot]]) {
|
|
60
|
+
const policy = await json(path.join(dir, 'controller', 'deadbyte-process-policy.json'));
|
|
61
|
+
assert(norm(policy?.roots?.core?.path ?? '.') === norm(dir), label + ' process policy core root not bound to its own checkout');
|
|
62
|
+
assert(norm(policy?.native_host?.path ?? '.') === norm(path.join(dir,'bin','deadbyte-process-host.exe')), label + ' native host not self-bound');
|
|
63
|
+
assert(norm(policy?.grant_file ?? '.') === norm(path.join(dir,'controller','runtime','process-grant.json')), label + ' grant path not self-bound');
|
|
64
|
+
assert(norm(policy?.state_dir ?? '.') === norm(path.join(dir,'controller','runtime','process-state')), label + ' state path not self-bound');
|
|
65
|
+
}
|
|
66
|
+
return { sourceFiles: source.size, protectedFiles: slot.size };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export async function assertR108NpmPublishReady(release, {root = defaultRoot,provenance = null} = {}) {
|
|
70
|
+
const rootPath = path.resolve(root);
|
|
71
|
+
const pkg = await json(path.join(rootPath,'package.json'));
|
|
72
|
+
const prov = provenance ?? await json(path.join(rootPath,'PROVENANCE.json'));
|
|
73
|
+
const lock = await json(path.join(rootPath,'package-lock.json'));
|
|
74
|
+
assert(pkg.name === 'deadbyte-mcp', 'package name mismatch');
|
|
75
|
+
assert([pkg.version,lock.version,lock.packages?.['']?.version,release.version,prov.version].every(v => v === '0.15.48'), 'package/release/provenance identity mismatch');
|
|
76
|
+
assert(release.train === 'R108', 'release train is not R108');
|
|
77
|
+
assert(release.r108_candidate?.status === 'CANDIDATE_UNSEALED', 'R108 pending-state metadata missing');
|
|
78
|
+
assert(release.source_predecessor?.version === '0.15.47' && prov.source_predecessor_version === '0.15.47', 'published source predecessor mismatch');
|
|
79
|
+
assert(release.parent?.version === '0.15.45' && release.runtime_predecessor?.version === '0.15.45', 'runtime predecessor mismatch');
|
|
80
|
+
for (const key of ['release_id','manifest_sha256','generation_manifest_sha256']) {
|
|
81
|
+
assert(release.parent?.[key] === prov['parent_'+key], 'sealed parent lineage mismatch at ' + key);
|
|
82
|
+
assert(release.runtime_predecessor?.[key] === release.parent?.[key], 'runtime predecessor drift at ' + key);
|
|
83
|
+
assert(prov.runtime_predecessor?.[key] === release.parent?.[key], 'provenance runtime predecessor drift at ' + key);
|
|
84
|
+
}
|
|
85
|
+
const proof = await json(path.join(rootPath,'testing',evidenceName));
|
|
86
|
+
assert(proof.schema === 'deadbyte.r108-protected-publication-evidence.v1', 'missing/fake R108 proof schema');
|
|
87
|
+
assert(proof.version === '0.15.48', 'proof version mismatch');
|
|
88
|
+
assert(norm(proof.candidate_root ?? '.') === norm(rootPath), 'proof candidate root changed');
|
|
89
|
+
assert(proof.test_exit_code === 0, 'test process did not finish with exit 0');
|
|
90
|
+
const testLog = await readFile(path.join(rootPath,'testing','r108-protected-full.tap'));
|
|
91
|
+
assert(sha256(testLog) === proof.test_log_sha256, 'test log bytes drifted');
|
|
92
|
+
const actualCounts = testCounts(testLog.toString('utf8'));
|
|
93
|
+
for (const key of ['tests','pass','fail','skipped']) assert(actualCounts[key] === proof.counts?.[key], 'proof disagrees with test log at '+key);
|
|
94
|
+
const parity = await slotParity(rootPath, proof.protected_root, proof.source_manifest_sha256, proof.protected_manifest_sha256);
|
|
95
|
+
assert(parity.sourceFiles >= 2339, 'manifest file count smaller than R107 baseline');
|
|
96
|
+
console.log('R108_PREPUBLISH_GATE_PASS '+JSON.stringify({version:'0.15.48',manifest_sha256:proof.source_manifest_sha256,test_log_sha256:proof.test_log_sha256,counts:actualCounts,source_files:parity.sourceFiles,protected_files:parity.protectedFiles}));
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const invoked = process.argv[1] && norm(process.argv[1]) === norm(fileURLToPath(import.meta.url));
|
|
101
|
+
if (invoked) {
|
|
102
|
+
try {
|
|
103
|
+
const command = process.argv[2] ?? 'verify';
|
|
104
|
+
if (command === 'record') {
|
|
105
|
+
const [rootArg,protectedArg,exitCodeArg] = process.argv.slice(3);
|
|
106
|
+
assert(rootArg && protectedArg && exitCodeArg !== undefined, 'usage: node scripts/npm-release-readiness-r108.mjs record ROOT PROTECTED_ROOT TEST_EXIT');
|
|
107
|
+
const root = path.resolve(rootArg), protectedRoot = path.resolve(protectedArg);
|
|
108
|
+
assert(Number(exitCodeArg) === 0, 'test exit code not zero');
|
|
109
|
+
const testLog = await readFile(path.join(root,'testing','r108-protected-full.tap'));
|
|
110
|
+
const counts = testCounts(testLog.toString('utf8'));
|
|
111
|
+
const sourceManifest = sha256(await readFile(path.join(root,'MANIFEST.SHA256')));
|
|
112
|
+
const protectedManifest = sha256(await readFile(path.join(protectedRoot,'MANIFEST.SHA256')));
|
|
113
|
+
await slotParity(root,protectedRoot,sourceManifest,protectedManifest);
|
|
114
|
+
const receipt = {
|
|
115
|
+
schema:'deadbyte.r108-protected-publication-evidence.v1',
|
|
116
|
+
version:'0.15.48',candidate_root:root,protected_root:protectedRoot,
|
|
117
|
+
source_manifest_sha256:sourceManifest,protected_manifest_sha256:protectedManifest,
|
|
118
|
+
test_log_sha256:sha256(testLog),test_exit_code:0,counts,
|
|
119
|
+
limitations:'Locally hashed regression and source/protected manifest parity; not a hardware attestation or independent signature.'
|
|
120
|
+
};
|
|
121
|
+
const proofPath=path.join(root,'testing',evidenceName);
|
|
122
|
+
await mkdir(path.dirname(proofPath),{recursive:true});
|
|
123
|
+
await writeFile(proofPath,JSON.stringify(receipt,null,2)+'\n',{flag:'wx'});
|
|
124
|
+
console.log('R108_PROOF_WRITTEN '+JSON.stringify({path:proofPath,...receipt}));
|
|
125
|
+
} else if (command === 'verify') {
|
|
126
|
+
const root = path.resolve(process.argv[3] ?? defaultRoot);
|
|
127
|
+
const release = await json(path.join(root,'RELEASE.json'));
|
|
128
|
+
await assertR108NpmPublishReady(release,{root});
|
|
129
|
+
} else throw new Error('unknown command '+command);
|
|
130
|
+
} catch(error) {
|
|
131
|
+
console.error(error?.stack || String(error));
|
|
132
|
+
process.exitCode=1;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/** R74 publication fence: verified 0.15.46 published source, 0.15.45 runtime parent,
|
|
2
|
+
* native Win32 kernel-exclusive lock, working tree manifest, and prepublish contract. */
|
|
3
|
+
import {createHash} from 'node:crypto';
|
|
4
|
+
import {readFile} from 'node:fs/promises';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import {verifyR74PrepublishContract} from './npm-prepublish-contract-r74.mjs';
|
|
7
|
+
|
|
8
|
+
const expectedRuntimeParent = Object.freeze({
|
|
9
|
+
version: '0.15.45',
|
|
10
|
+
release_id: 'v0.15.45-34aa8d1241cfb8bf',
|
|
11
|
+
manifest_sha256: '34aa8d1241cfb8bf0b5ba303bf3c06e0e9e1bf01f64457bca659d6dd4d634fdc',
|
|
12
|
+
generation_manifest_sha256: 'ff2b73410062688651abd7bc2dd7b2b8c87810a28e3808bfe1f4352a25b0e6e4'
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const sha = bytes => createHash('sha256').update(bytes).digest('hex');
|
|
16
|
+
function reject(reason) { throw new Error(`PUBLISH_BLOCKED R74 ${reason}`); }
|
|
17
|
+
|
|
18
|
+
export async function assertR74NpmPublishReady(release, {root} = {}) {
|
|
19
|
+
if (!root || !path.isAbsolute(root)) reject('candidate root missing');
|
|
20
|
+
if (release?.version !== '0.15.47' || release?.train !== 'R74' || release?.name !== 'deadbyte-mcp') {
|
|
21
|
+
reject('release identity or train mismatch; require 0.15.47 / R74');
|
|
22
|
+
}
|
|
23
|
+
if (release?.parent?.version !== expectedRuntimeParent.version ||
|
|
24
|
+
release?.parent?.release_id !== expectedRuntimeParent.release_id ||
|
|
25
|
+
release?.parent?.manifest_sha256 !== expectedRuntimeParent.manifest_sha256) {
|
|
26
|
+
reject('runtime predecessor parent mismatch; require sealed 0.15.45 lineage');
|
|
27
|
+
}
|
|
28
|
+
if (release?.source_predecessor?.version !== '0.15.46') {
|
|
29
|
+
reject('published source predecessor mismatch; require 0.15.46');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const contractResult = await verifyR74PrepublishContract({rootPath: root});
|
|
33
|
+
if (!contractResult?.valid) {
|
|
34
|
+
reject('prepublish contract verification failed');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const manifest = await readFile(path.join(root, 'MANIFEST.SHA256')).catch(() => reject('candidate manifest unavailable'));
|
|
38
|
+
const manifestSha256 = sha(manifest);
|
|
39
|
+
|
|
40
|
+
return Object.freeze({
|
|
41
|
+
status: 'R74_RELEASE_READY_EVIDENCE_BOUND',
|
|
42
|
+
version: '0.15.47',
|
|
43
|
+
manifest_sha256: manifestSha256,
|
|
44
|
+
prepublish_contract: contractResult
|
|
45
|
+
});
|
|
46
|
+
}
|
|
@@ -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}));
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
|
|
6
|
+
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
7
|
+
const pkg = JSON.parse(readFileSync(path.join(root, 'package.json'), 'utf8'));
|
|
8
|
+
const bin = typeof pkg.bin === 'string' ? { [pkg.name]: pkg.bin } : pkg.bin;
|
|
9
|
+
if (!bin || typeof bin !== 'object' || Array.isArray(bin) || !Object.keys(bin).length)
|
|
10
|
+
throw new Error('BIN_INTEGRITY: package.json.bin missing or invalid');
|
|
11
|
+
const lines = readFileSync(path.join(root, 'MANIFEST.SHA256'), 'utf8').split(/\r?\n/).filter(Boolean);
|
|
12
|
+
const entries = new Map();
|
|
13
|
+
for (const line of lines) {
|
|
14
|
+
const m = /^([0-9a-f]{64}) (.+)$/.exec(line);
|
|
15
|
+
if (!m || entries.has(m[2])) throw new Error('BIN_INTEGRITY: invalid or duplicate manifest entry');
|
|
16
|
+
entries.set(m[2], m[1]);
|
|
17
|
+
}
|
|
18
|
+
for (const [name, relRaw] of Object.entries(bin)) {
|
|
19
|
+
if (typeof relRaw !== 'string' || !relRaw || relRaw.includes('\\'))
|
|
20
|
+
throw new Error(`BIN_INTEGRITY: invalid bin path for ${name}`);
|
|
21
|
+
const rel = relRaw.replace(/^\.\//, '');
|
|
22
|
+
const full = path.resolve(root, rel);
|
|
23
|
+
if (!full.startsWith(root + path.sep) || !entries.has(rel))
|
|
24
|
+
throw new Error(`BIN_INTEGRITY: unmanifested or escaping bin '${name}'`);
|
|
25
|
+
const bytes = readFileSync(full);
|
|
26
|
+
const firstLf = bytes.indexOf(0x0a);
|
|
27
|
+
if (firstLf < 2 || bytes[0] !== 0x23 || bytes[1] !== 0x21 || bytes[firstLf - 1] === 0x0d)
|
|
28
|
+
throw new Error(`BIN_INTEGRITY: bin '${name}' must begin with shebang LF (no CRLF)`);
|
|
29
|
+
const sha = createHash('sha256').update(bytes).digest('hex');
|
|
30
|
+
if (sha !== entries.get(rel))
|
|
31
|
+
throw new Error(`BIN_INTEGRITY: manifest and bin bytes differ for '${name}'`);
|
|
32
|
+
process.stdout.write(`BIN_INTEGRITY_PASS ${name} ${rel} ${sha}\n`);
|
|
33
|
+
}
|
|
@@ -18,7 +18,7 @@ const requiredNativePayload = [
|
|
|
18
18
|
'bin/bootstrap-kernel32.exe', 'bin/bootstrap-advapi32.exe',
|
|
19
19
|
'bin/appcontainer-stage.exe', 'bin/bootstrap-exitcode.exe',
|
|
20
20
|
'bin/child-control-stage.exe', 'bin/child-control-probe.exe',
|
|
21
|
-
'bin/deadbyte-exec.exe', 'bin/contained-transform-worker.exe',
|
|
21
|
+
'bin/deadbyte-exec.exe', 'bin/deadbyte-epoch-lock-r85.node', 'bin/contained-transform-worker.exe',
|
|
22
22
|
'bin/contained-reverse-worker.exe', 'bin/deadbyte-tunnel-host.exe', 'bin/WORKER-REGISTRY.txt'
|
|
23
23
|
];
|
|
24
24
|
const requiredBundledDependencyRoots = [
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#requires -Version 5.1
|
|
2
2
|
# R70 verifier artifact: runs the candidate test suite without altering release manifests.
|
|
3
|
-
param([switch]$SelfTest)
|
|
3
|
+
param([switch]$SelfTest,[string]$EvidenceRoot='')
|
|
4
4
|
Set-StrictMode -Version Latest
|
|
5
5
|
$ErrorActionPreference = 'Continue'
|
|
6
6
|
Set-Location -LiteralPath (Split-Path -Parent $PSScriptRoot)
|
|
@@ -36,7 +36,17 @@ $selected = @($lines | Where-Object {
|
|
|
36
36
|
$summary = @($selected | Select-Object -Last 140 | ForEach-Object {
|
|
37
37
|
if ($_.Length -gt 512) { $_.Substring(0, 512) } else { $_ }
|
|
38
38
|
})
|
|
39
|
-
$
|
|
39
|
+
if ($EvidenceRoot -and -not $SelfTest) { throw 'EvidenceRoot override is self-test only' }
|
|
40
|
+
$artifactDir = if ($EvidenceRoot) {
|
|
41
|
+
if (-not [System.IO.Path]::IsPathRooted($EvidenceRoot)) { throw 'EvidenceRoot must be absolute' }
|
|
42
|
+
$resolved = [System.IO.Path]::GetFullPath($EvidenceRoot)
|
|
43
|
+
$checkout = [System.IO.Path]::GetFullPath((Get-Location).Path).TrimEnd('\')
|
|
44
|
+
if ($resolved.TrimEnd('\').Equals($checkout,[System.StringComparison]::OrdinalIgnoreCase) -or
|
|
45
|
+
$resolved.StartsWith($checkout + '\',[System.StringComparison]::OrdinalIgnoreCase)) {
|
|
46
|
+
throw 'EvidenceRoot self-test override must be outside the checkout'
|
|
47
|
+
}
|
|
48
|
+
$resolved
|
|
49
|
+
} else { Join-Path (Get-Location).Path 'testing\r70-verifier' }
|
|
40
50
|
if (-not (Test-Path -LiteralPath $artifactDir -PathType Container)) {
|
|
41
51
|
New-Item -ItemType Directory -Path $artifactDir -Force | Out-Null
|
|
42
52
|
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import {createHash} from 'node:crypto';
|
|
2
|
+
import {lstat,open,realpath} from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
const FIELDS=Object.freeze({process:'grant_file',coding:'lease_file',autonomous:'lease_file',desktop:'grant_file',host:'arm_file'});
|
|
6
|
+
const sha=bytes=>createHash('sha256').update(bytes).digest('hex');
|
|
7
|
+
const within=(root,target)=>{const relative=path.relative(root,target);return relative!==''&&!relative.startsWith('..'+path.sep)&&relative!=='..'&&!path.isAbsolute(relative);};
|
|
8
|
+
const sameFile=(left,right)=>left.isFile()&&right.isFile()&&!left.isSymbolicLink()&&!right.isSymbolicLink()&&
|
|
9
|
+
left.dev===right.dev&&left.ino===right.ino&&left.size===right.size&&left.mtimeMs===right.mtimeMs&&
|
|
10
|
+
left.ctimeMs===right.ctimeMs&&left.birthtimeMs===right.birthtimeMs;
|
|
11
|
+
async function readRegularPinned(file,label){
|
|
12
|
+
const before=await lstat(file);
|
|
13
|
+
if(!before.isFile()||before.isSymbolicLink())throw new Error(`${label} file type invalid`);
|
|
14
|
+
const handle=await open(file,'r');
|
|
15
|
+
try{
|
|
16
|
+
const opened=await handle.stat();
|
|
17
|
+
if(!sameFile(before,opened))throw new Error(`${label} identity changed before read`);
|
|
18
|
+
const bytes=await handle.readFile();
|
|
19
|
+
const after=await handle.stat(),pathAfter=await lstat(file);
|
|
20
|
+
if(!sameFile(opened,after)||!sameFile(after,pathAfter))throw new Error(`${label} identity changed during read`);
|
|
21
|
+
return bytes;
|
|
22
|
+
}finally{await handle.close();}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function observeAuthorityEvidence({stateRoot,policyPaths,policySha256,authority}){
|
|
26
|
+
const runtimeRoot=await realpath(path.join(stateRoot,'runtime'));
|
|
27
|
+
const result={};
|
|
28
|
+
for(const [plane,field] of Object.entries(FIELDS)){
|
|
29
|
+
const policyPath=policyPaths?.[plane],expectedHash=policySha256?.[plane],entry=authority?.[plane];
|
|
30
|
+
if(typeof policyPath!=='string'||!path.isAbsolute(policyPath)||!(/^[0-9a-f]{64}$/).test(expectedHash??''))
|
|
31
|
+
throw new Error(`${plane} policy identity incomplete`);
|
|
32
|
+
const policyBytes=await readRegularPinned(policyPath,`${plane} policy`);
|
|
33
|
+
if(sha(policyBytes)!==expectedHash)throw new Error(`${plane} policy digest changed during observation`);
|
|
34
|
+
let policy;
|
|
35
|
+
try{policy=JSON.parse(policyBytes.toString('utf8').replace(/^\uFEFF/,''));}
|
|
36
|
+
catch{throw new Error(`${plane} policy JSON invalid`);}
|
|
37
|
+
const schemaOk=plane==='host'?['deadbyte.host-policy.v1','deadbyte.host-policy.v2'].includes(policy?.schema):
|
|
38
|
+
policy?.schema===`deadbyte.${plane}-policy.v1`;
|
|
39
|
+
if(!schemaOk||policy.enabled!==true||
|
|
40
|
+
typeof policy[field]!=='string'||!path.isAbsolute(policy[field]))
|
|
41
|
+
throw new Error(`${plane} policy grant path invalid`);
|
|
42
|
+
const grantPath=path.resolve(policy[field]);
|
|
43
|
+
const parent=await realpath(path.dirname(grantPath));
|
|
44
|
+
if(!within(runtimeRoot,parent)&&parent!==runtimeRoot)throw new Error(`${plane} policy grant path outside runtime`);
|
|
45
|
+
const checkedPath=path.join(parent,path.basename(grantPath));
|
|
46
|
+
let grantInfo;
|
|
47
|
+
try{grantInfo=await lstat(checkedPath);}
|
|
48
|
+
catch(error){if(error?.code!=='ENOENT')throw error;}
|
|
49
|
+
const grantAbsent=grantInfo===undefined;
|
|
50
|
+
if(grantInfo&&(!grantInfo.isFile()||grantInfo.isSymbolicLink()))throw new Error(`${plane} grant file type invalid`);
|
|
51
|
+
const validEntry=entry&&entry.configured===true&&typeof entry.armed==='boolean';
|
|
52
|
+
const validDisarmed=validEntry&&!entry.armed&&grantAbsent&&
|
|
53
|
+
(entry.reason==='disarmed'||(plane==='autonomous'&&entry.reason==='coding-disarmed'));
|
|
54
|
+
const validArmed=validEntry&&entry.armed&&!grantAbsent&&entry.reason==='standing-grant'&&
|
|
55
|
+
Number.isSafeInteger(entry.epoch_seq)&&entry.epoch_seq>0&&/^[0-9a-f]{64}$/.test(entry.epoch_sha256??'');
|
|
56
|
+
const grantSha=grantInfo?sha(await readRegularPinned(checkedPath,`${plane} grant`)):null;
|
|
57
|
+
let grantFinal;
|
|
58
|
+
try{grantFinal=await lstat(checkedPath);}
|
|
59
|
+
catch(error){if(error?.code!=='ENOENT')throw error;}
|
|
60
|
+
if(grantAbsent?grantFinal!==undefined:!grantFinal||!sameFile(grantInfo,grantFinal))
|
|
61
|
+
throw new Error(`${plane} grant identity changed during observation`);
|
|
62
|
+
result[plane]=Object.freeze({state:validDisarmed?'VERIFIED_DISARMED':validArmed?'VERIFIED_ARMED':'UNVERIFIED',
|
|
63
|
+
policy_sha256:expectedHash,grant_absent:grantAbsent,
|
|
64
|
+
...(grantSha?{grant_sha256:grantSha}:{})});
|
|
65
|
+
}
|
|
66
|
+
return Object.freeze(result);
|
|
67
|
+
}
|
|
@@ -3,6 +3,7 @@ import {open,readFile,rm} from 'node:fs/promises';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import {canonicalJson} from './canonical-json.mjs';
|
|
5
5
|
import {decodeUtf8Strict} from './text-policy.mjs';
|
|
6
|
+
import {deletePinnedRemoteMarker} from './foreground-marker-cas-win32.mjs';
|
|
6
7
|
import {
|
|
7
8
|
captureStandingGrant,compareSecurityEnvelopes,createAuthorityEpochStore,deriveSecurityEnvelope
|
|
8
9
|
} from './authority-continuity-r65.mjs';
|
|
@@ -41,6 +42,17 @@ async function writeExclusive(file,bytes){
|
|
|
41
42
|
finally{await handle.close();}
|
|
42
43
|
}
|
|
43
44
|
async function removeIfExact(file,expectedSha256){
|
|
45
|
+
// The Windows native disposition pins a single handle from read/hash through deletion.
|
|
46
|
+
// Never use a pathname removal after confirming only a previous snapshot.
|
|
47
|
+
if(process.platform==='win32'){
|
|
48
|
+
const verdict=deletePinnedRemoteMarker(file,expectedSha256);
|
|
49
|
+
if(verdict==='REMOVED')return {removed:true,reason:'matched'};
|
|
50
|
+
if(verdict==='MISSING')return {removed:false,reason:'absent'};
|
|
51
|
+
if(verdict==='CHANGED')return {removed:false,reason:'replacement'};
|
|
52
|
+
throw new Error('authority grant native deletion verdict invalid');
|
|
53
|
+
}
|
|
54
|
+
// Legacy non-Windows behavior is retained for source/test compatibility.
|
|
55
|
+
// It is NOT a cross-platform pinned-deletion security guarantee.
|
|
44
56
|
const current=await readOptional(file);
|
|
45
57
|
if(current===null)return {removed:false,reason:'absent'};
|
|
46
58
|
if(sha(current)!==expectedSha256)return {removed:false,reason:'replacement'};
|
|
@@ -193,10 +205,11 @@ export function createEpochNativeContinuityEngine({stateRoot,predecessorPolicyPa
|
|
|
193
205
|
if(conflict==='REVOKED_DURING_UPDATE')await removeIfExact(file,record.grant_sha256).catch(()=>{});
|
|
194
206
|
outcomes[plane]=conflict;reauth.push(plane);continue;
|
|
195
207
|
}
|
|
196
|
-
const
|
|
197
|
-
if(
|
|
198
|
-
|
|
199
|
-
|
|
208
|
+
const removed=await removeIfExact(file,record.grant_sha256);
|
|
209
|
+
if(!removed.removed){
|
|
210
|
+
outcomes[plane]=removed.reason==='absent'?'REVOKED_DURING_UPDATE':'REPLACED';
|
|
211
|
+
reauth.push(plane);continue;
|
|
212
|
+
}
|
|
200
213
|
const prepared=buildSuccessorGrant({plane,record,successorPolicyBytes,successorPolicy});
|
|
201
214
|
try{await writeExclusive(file,prepared.bytes);}
|
|
202
215
|
catch(error){if(error?.code==='EEXIST'){outcomes[plane]='REPLACED';reauth.push(plane);continue;}throw error;}
|
|
@@ -47,6 +47,19 @@ export function classifyActivationPreflight({pointer,expectedParent,candidate,ex
|
|
|
47
47
|
if(PLANES.some(plane=>typeof authority[plane]!=='boolean'))return Object.freeze({state:'UNKNOWN',safe_to_activate:false,reason:'authority observation incomplete'});
|
|
48
48
|
const subject={pointer,expectedParent,candidate,expectedCandidate,observation};
|
|
49
49
|
const bound={subject_sha256:digest(subject),authority:Object.freeze(authority)};
|
|
50
|
+
for(const plane of PLANES){
|
|
51
|
+
const entry=observation?.authority?.[plane],proof=observation?.authority_proof?.[plane];
|
|
52
|
+
if(entry&&typeof entry==='object'){
|
|
53
|
+
const knownReason=entry.armed?entry.reason==='standing-grant':entry.reason==='disarmed'||entry.reason==='coding-disarmed';
|
|
54
|
+
if(!knownReason)return Object.freeze({state:'AUTHORITY_UNVERIFIED',safe_to_activate:false,reason:`${plane} authority reason unverified`,...bound});
|
|
55
|
+
}
|
|
56
|
+
if(!proof)
|
|
57
|
+
return Object.freeze({state:'AUTHORITY_UNVERIFIED',safe_to_activate:false,reason:`${plane} authority proof missing`,...bound});
|
|
58
|
+
if(proof&&(proof.policy_sha256!==observation?.policy_sha256?.[plane]||
|
|
59
|
+
proof.state!==(authority[plane]?'VERIFIED_ARMED':'VERIFIED_DISARMED')||
|
|
60
|
+
(!authority[plane]&&proof.grant_absent!==true)))
|
|
61
|
+
return Object.freeze({state:'AUTHORITY_UNVERIFIED',safe_to_activate:false,reason:`${plane} authority proof unverified`,...bound});
|
|
62
|
+
}
|
|
50
63
|
if(PLANES.some(plane=>!HEX64.test(String(observation?.policy_sha256?.[plane]))))
|
|
51
64
|
return Object.freeze({state:'UNKNOWN',safe_to_activate:false,reason:'policy hash observation incomplete',...bound});
|
|
52
65
|
if(pointer.release_id!==expectedParent.release_id||pointer.version!==expectedParent.version||
|