mcp-pacemaker 2.0.2 → 2.1.1
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/CHANGELOG.md +171 -1
- package/CONTRIBUTING.md +46 -13
- package/README.md +109 -524
- package/THIRD_PARTY_NOTICES.txt +1 -28
- package/autostart/windows/account-task.ps1 +167 -0
- package/autostart/windows/inspect-task.ps1 +42 -0
- package/autostart/windows/inspect-worker-process-sessions.ps1 +155 -0
- package/autostart/windows/legacy-access.ps1 +359 -0
- package/autostart/windows/legacy-task.ps1 +301 -0
- package/autostart/windows/register-task.ps1 +11 -2
- package/bin/account-task-adapter.mjs +47 -0
- package/bin/account-upgrade-controller.mjs +198 -0
- package/bin/account-upgrade-worker.mjs +141 -0
- package/bin/account-worker-session.mjs +215 -0
- package/bin/cli-dispatch.mjs +154 -0
- package/bin/cli-main.mjs +1063 -0
- package/bin/cli-selection.mjs +43 -0
- package/bin/cli.mjs +7 -876
- package/bin/dependency-lock.json +642 -0
- package/bin/legacy-1.3.json +32 -0
- package/bin/legacy-access.mjs +73 -0
- package/bin/legacy-installation.mjs +273 -0
- package/bin/legacy-task.mjs +76 -0
- package/bin/legacy-upgrade.mjs +527 -0
- package/bin/managed-runtime.mjs +170 -0
- package/bin/managed-state.mjs +257 -0
- package/bin/managed-upgrade.mjs +581 -0
- package/bin/mcp-bridge.mjs +150 -4
- package/bin/pooling-batches.mjs +13 -0
- package/bin/pooling-writer.mjs +4 -0
- package/bin/prewarm.mjs +59 -25
- package/bin/runtime-context.mjs +100 -0
- package/bin/service-control.mjs +32 -4
- package/bin/stable-launcher.mjs +54 -0
- package/bin/task-only-controller.mjs +226 -0
- package/bin/task-scope.mjs +65 -0
- package/bin/task-transaction-protocol.mjs +173 -0
- package/bin/top.mjs +22 -10
- package/bin/update-check.mjs +13 -2
- package/bin/upgrade-autostart.mjs +47 -0
- package/bin/upgrade-capability.json +9 -0
- package/bin/upgrade-dependencies.mjs +183 -0
- package/bin/upgrade-package.mjs +205 -0
- package/bin/windows-legacy/LegacyProcessBroker.build.json +42 -0
- package/bin/windows-legacy/LegacyProcessBroker.exe +0 -0
- package/bin/windows-legacy/src/AssemblyInfo.cs +8 -0
- package/bin/windows-legacy/src/LegacyNative.cs +832 -0
- package/bin/windows-legacy/src/LegacyProcessBroker.cs +961 -0
- package/bin/windows-legacy-process.mjs +248 -0
- package/bin/windows-lifetime/ProcessLifetimeHelper.build.json +38 -0
- package/bin/windows-lifetime/ProcessLifetimeHelper.exe +0 -0
- package/bin/windows-lifetime/src/AssemblyInfo.cs +8 -0
- package/bin/windows-lifetime/src/ProcessLifetimeHelper.cs +468 -0
- package/bin/windows-process-lifetime.mjs +218 -0
- package/bin/windows-task-channel/TaskChannelGuard.build.json +45 -0
- package/bin/windows-task-channel/TaskChannelGuard.exe +0 -0
- package/bin/windows-task-channel/src/AssemblyInfo.cs +8 -0
- package/bin/windows-task-channel/src/ChannelCliContext.cs +210 -0
- package/bin/windows-task-channel/src/ChannelFiles.cs +231 -0
- package/bin/windows-task-channel/src/ChannelNative.cs +477 -0
- package/bin/windows-task-channel/src/ChannelSession.cs +203 -0
- package/bin/windows-task-channel/src/TaskChannelGuard.cs +686 -0
- package/bin/windows-task-channel.mjs +335 -0
- package/docs/account-upgrades.md +218 -0
- package/docs/compatibility.md +39 -0
- package/docs/configuration.md +146 -0
- package/docs/dashboards.md +125 -0
- package/docs/execution-context.md +119 -0
- package/docs/http-api.md +47 -0
- package/docs/install.md +52 -0
- package/docs/operations.md +99 -0
- package/docs/upgrades.md +324 -0
- package/package.json +19 -5
- package/supervisor/supervise.mjs +226 -40
- package/tools/account-upgrade/qualify-two-account.ps1 +66 -0
- package/tools/windows-legacy/README.md +251 -0
- package/tools/windows-legacy/build.ps1 +122 -0
- package/tools/windows-legacy/inventory.mjs +92 -0
- package/tools/windows-process-lifetime/README.md +119 -0
- package/tools/windows-process-lifetime/build.ps1 +122 -0
- package/tools/windows-process-lifetime/inventory.mjs +86 -0
- package/tools/windows-task-channel/README.md +329 -0
- package/tools/windows-task-channel/build.ps1 +107 -0
- package/tools/windows-task-channel/inventory.mjs +117 -0
- package/ui/dist/THIRD_PARTY_NOTICES.txt +1 -28
- package/ui/dist/assets/{index-CgPruWYi.js → index-CcwMo2sI.js} +1 -1
- package/ui/dist/assets/index-PVcuDNrN.css +1 -0
- package/ui/dist/index.html +2 -2
- package/ui/dist/third-party-manifest.json +37 -25
- package/ui/dist/assets/index-CaLPg0c-.css +0 -1
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { existsSync, lstatSync, readFileSync, readdirSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { join, relative } from 'node:path';
|
|
5
|
+
import { isDeepStrictEqual, promisify } from 'node:util';
|
|
6
|
+
import semver from 'semver';
|
|
7
|
+
import { npmCliPath } from './update-check.mjs';
|
|
8
|
+
import { PoolingFiles } from './pooling-files.mjs';
|
|
9
|
+
|
|
10
|
+
const execute = promisify(execFile);
|
|
11
|
+
const PUBLIC_REGISTRY = 'https://registry.npmjs.org/';
|
|
12
|
+
const policies = [
|
|
13
|
+
'registry', 'replace-registry-host', 'offline', 'prefer-offline', 'strict-ssl', 'ca', 'cafile',
|
|
14
|
+
'proxy', 'https-proxy', 'noproxy', 'local-address', 'min-release-age', 'min-release-age-exclude',
|
|
15
|
+
'allow-directory', 'allow-file', 'allow-git', 'allow-remote', 'allow-scripts', 'strict-dep-builds',
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
export function dependencyInventory(root) {
|
|
19
|
+
const files = [];
|
|
20
|
+
const walk = path => {
|
|
21
|
+
const stat = lstatSync(path);
|
|
22
|
+
if (stat.isSymbolicLink()) throw new Error('Linked dependency inputs are unsupported.');
|
|
23
|
+
if (stat.isDirectory()) {
|
|
24
|
+
for (const name of readdirSync(path)) walk(join(path, name));
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
if (!stat.isFile() ||
|
|
28
|
+
stat.nlink !== 1) throw new Error('Unsupported dependency input.');
|
|
29
|
+
files.push({ path: relative(root, path).replaceAll('\\', '/'),
|
|
30
|
+
sha256: createHash('sha256').update(readFileSync(path)).digest('hex') });
|
|
31
|
+
};
|
|
32
|
+
walk(join(root, 'package-lock.json'));
|
|
33
|
+
walk(join(root, 'node_modules'));
|
|
34
|
+
return files;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function validateDependencyLock(root) {
|
|
38
|
+
const pkg = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8'));
|
|
39
|
+
const lock = JSON.parse(readFileSync(join(root, 'package-lock.json'), 'utf8'));
|
|
40
|
+
if (lock.lockfileVersion !== 3 ||
|
|
41
|
+
lock.name !== pkg.name ||
|
|
42
|
+
lock.version !== pkg.version ||
|
|
43
|
+
!isDeepStrictEqual(lock.packages?.['']?.dependencies, pkg.dependencies)) {
|
|
44
|
+
throw new Error('Target dependency lock does not match the selected package.');
|
|
45
|
+
}
|
|
46
|
+
for (const [path, entry] of Object.entries(lock.packages)) {
|
|
47
|
+
if (!path) continue;
|
|
48
|
+
if (!path.startsWith('node_modules/') ||
|
|
49
|
+
path.split('/').some(part => !part || part === '.' || part === '..') ||
|
|
50
|
+
entry.link ||
|
|
51
|
+
semver.valid(entry.version) !== entry.version) throw new Error('Unverifiable target dependency lock entry.');
|
|
52
|
+
if (typeof entry.integrity !== 'string' ||
|
|
53
|
+
!entry.integrity.startsWith('sha512-')) {
|
|
54
|
+
const found = typeof entry.integrity === 'string' ? entry.integrity.split('-')[0] : 'no';
|
|
55
|
+
throw new Error(`Unverifiable target dependency lock entry: ${path}@${entry.version} has ${found} integrity from the registry; SHA-512 is required.`);
|
|
56
|
+
}
|
|
57
|
+
const integrity = Buffer.from(entry.integrity.slice(7), 'base64');
|
|
58
|
+
if (integrity.length !== 64 ||
|
|
59
|
+
`sha512-${integrity.toString('base64')}` !== entry.integrity) throw new Error('Invalid dependency archive integrity.');
|
|
60
|
+
const url = new URL(entry.resolved);
|
|
61
|
+
if (!['https:', 'http:'].includes(url.protocol) ||
|
|
62
|
+
url.username ||
|
|
63
|
+
url.password ||
|
|
64
|
+
url.hash) throw new Error('Unsupported dependency archive source.');
|
|
65
|
+
}
|
|
66
|
+
return lock;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// From 2.1.1 a target ships the SHA-512 lock it was released and tested with. npm ci then
|
|
70
|
+
// verifies every archive against it, so registry metadata (SHA-1 on some mirrors) and newer
|
|
71
|
+
// in-range releases cannot change the staged dependency set.
|
|
72
|
+
export function bundledDependencyLock(root) {
|
|
73
|
+
const capabilityPath = join(root, 'bin', 'upgrade-capability.json');
|
|
74
|
+
if (!existsSync(capabilityPath)) return null;
|
|
75
|
+
const capability = JSON.parse(readFileSync(capabilityPath, 'utf8'));
|
|
76
|
+
if (capability.dependencyLock !== 1) return null;
|
|
77
|
+
const path = join(root, 'bin', 'dependency-lock.json');
|
|
78
|
+
const stat = existsSync(path) ? lstatSync(path) : null;
|
|
79
|
+
if (!stat?.isFile() ||
|
|
80
|
+
stat.size === 0) throw new Error('Target declares a bundled dependency lock but does not contain it.');
|
|
81
|
+
return readFileSync(path);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// npm points public-registry lock URLs at the configured registry itself, unless
|
|
85
|
+
// replace-registry-host is "never" or names only another host or URL prefix.
|
|
86
|
+
export function npmMapsPublicRegistry(setting) {
|
|
87
|
+
const value = String(setting ?? 'npmjs');
|
|
88
|
+
return ['npmjs', 'always'].includes(value) ||
|
|
89
|
+
value.includes('registry.npmjs.org');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// The bundled lock names the public registry. When npm will not map those URLs, point each
|
|
93
|
+
// entry at the registry that resolving it would have used: its scope's registry, else the
|
|
94
|
+
// configured one. Integrity covers the archive bytes, not the URL.
|
|
95
|
+
export function registryLock(bytes, registry, scopes = {}) {
|
|
96
|
+
const lock = JSON.parse(bytes.toString('utf8'));
|
|
97
|
+
let changed = false;
|
|
98
|
+
for (const [path, entry] of Object.entries(lock.packages ?? {})) {
|
|
99
|
+
const publicEntry = path &&
|
|
100
|
+
typeof entry.resolved === 'string' &&
|
|
101
|
+
entry.resolved.startsWith(PUBLIC_REGISTRY);
|
|
102
|
+
if (!publicEntry) continue;
|
|
103
|
+
const name = path.slice(path.lastIndexOf('node_modules/') + 'node_modules/'.length);
|
|
104
|
+
const target = (name.startsWith('@') ? scopes[name.split('/')[0]] : undefined) ?? registry;
|
|
105
|
+
if (!target) continue;
|
|
106
|
+
const base = target.endsWith('/') ? target : `${target}/`;
|
|
107
|
+
if (base === PUBLIC_REGISTRY) continue;
|
|
108
|
+
entry.resolved = base + entry.resolved.slice(PUBLIC_REGISTRY.length);
|
|
109
|
+
changed = true;
|
|
110
|
+
}
|
|
111
|
+
return changed ? Buffer.from(`${JSON.stringify(lock, null, 2)}\n`) : bytes;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export async function stageCliDependencies(root, { registry, cache, run = execute } = {}) {
|
|
115
|
+
const cli = npmCliPath();
|
|
116
|
+
const call = async (args, options = {}) => {
|
|
117
|
+
try {
|
|
118
|
+
return await run(process.execPath, [cli, ...args], {
|
|
119
|
+
encoding: 'utf8', windowsHide: true, timeout: 120000, maxBuffer: 8 * 1024 * 1024, ...options,
|
|
120
|
+
});
|
|
121
|
+
} catch {
|
|
122
|
+
throw new Error('CLI dependencies were refused or unavailable under configured npm policy. No fallback or policy relaxation was attempted.');
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
const configuration = JSON.parse((await call(['config', 'list', '--json'])).stdout);
|
|
126
|
+
const prefix = (await call(['prefix'])).stdout.trim();
|
|
127
|
+
if (!prefix ||
|
|
128
|
+
!existsSync(prefix)) throw new Error('Caller npm project context could not be verified.');
|
|
129
|
+
const env = { ...process.env };
|
|
130
|
+
for (const [key, value] of Object.entries(configuration)) {
|
|
131
|
+
const scoped = key.startsWith('@') && key.endsWith(':registry');
|
|
132
|
+
if (!policies.includes(key) &&
|
|
133
|
+
!scoped) continue;
|
|
134
|
+
if (value === null ||
|
|
135
|
+
value === undefined) continue;
|
|
136
|
+
if (Array.isArray(value)) continue;
|
|
137
|
+
env[`npm_config_${key.replaceAll('-', '_')}`] = String(value);
|
|
138
|
+
}
|
|
139
|
+
if (configuration['strict-ssl'] === false) throw new Error('Managed dependency staging cannot disable TLS verification.');
|
|
140
|
+
const projectConfig = join(prefix, '.npmrc');
|
|
141
|
+
const stagedConfig = join(root, '.npmrc');
|
|
142
|
+
if (existsSync(stagedConfig)) throw new Error('Unexpected target npm configuration.');
|
|
143
|
+
const bundled = bundledDependencyLock(root);
|
|
144
|
+
if (bundled &&
|
|
145
|
+
existsSync(join(root, 'package-lock.json'))) throw new Error('Unexpected target dependency lock.');
|
|
146
|
+
const scopes = Object.fromEntries(Object.entries(configuration)
|
|
147
|
+
.filter(([key, value]) => typeof value === 'string' &&
|
|
148
|
+
/^@[^:/]+:registry$/.test(key))
|
|
149
|
+
.map(([key, value]) => [key.slice(0, -':registry'.length), value]));
|
|
150
|
+
const manifest = readFileSync(join(root, 'package.json'));
|
|
151
|
+
if (existsSync(projectConfig)) {
|
|
152
|
+
const files = new PoolingFiles(projectConfig);
|
|
153
|
+
const identity = files.inspect(projectConfig);
|
|
154
|
+
files.stage(stagedConfig, projectConfig, identity, readFileSync(projectConfig));
|
|
155
|
+
}
|
|
156
|
+
const args = ['--ignore-scripts', '--omit=dev', '--no-audit', '--no-fund', '--bin-links=false',
|
|
157
|
+
'--cache', cache, '--registry', registry];
|
|
158
|
+
try {
|
|
159
|
+
const effective = JSON.parse((await call(['config', 'list', '--json'], { cwd: root, env })).stdout);
|
|
160
|
+
for (const key of Object.keys(configuration)) {
|
|
161
|
+
if (!policies.includes(key) &&
|
|
162
|
+
!(key.startsWith('@') && key.endsWith(':registry'))) continue;
|
|
163
|
+
if (!isDeepStrictEqual(configuration[key], effective[key])) {
|
|
164
|
+
throw new Error('Effective npm policy changed in dependency staging; no dependency resolution was attempted.');
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (bundled) {
|
|
168
|
+
const staged = npmMapsPublicRegistry(configuration['replace-registry-host'])
|
|
169
|
+
? bundled
|
|
170
|
+
: registryLock(bundled, registry, scopes);
|
|
171
|
+
writeFileSync(join(root, 'package-lock.json'), staged, { flag: 'wx' });
|
|
172
|
+
} else await call(['install', '--package-lock-only', ...args], { cwd: root, env });
|
|
173
|
+
validateDependencyLock(root);
|
|
174
|
+
const lock = readFileSync(join(root, 'package-lock.json'));
|
|
175
|
+
await call(['ci', ...args], { cwd: root, env });
|
|
176
|
+
if (!readFileSync(join(root, 'package.json')).equals(manifest) ||
|
|
177
|
+
!readFileSync(join(root, 'package-lock.json')).equals(lock)) throw new Error('Dependency staging changed the verified manifest or lock.');
|
|
178
|
+
await call(['ls', '--omit=dev', '--json'], { cwd: root, env });
|
|
179
|
+
return dependencyInventory(root);
|
|
180
|
+
} finally {
|
|
181
|
+
if (existsSync(stagedConfig)) unlinkSync(stagedConfig);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { createHash, timingSafeEqual } from 'node:crypto';
|
|
3
|
+
import { mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { dirname, join } from 'node:path';
|
|
5
|
+
import { promisify } from 'node:util';
|
|
6
|
+
import { gunzipSync } from 'node:zlib';
|
|
7
|
+
import semver from 'semver';
|
|
8
|
+
import { npmCliPath, readNpmMetadata, validateRegistry } from './update-check.mjs';
|
|
9
|
+
|
|
10
|
+
const runFile = promisify(execFile);
|
|
11
|
+
const required = [
|
|
12
|
+
'bin/cli.mjs', 'bin/cli-main.mjs', 'bin/cli-dispatch.mjs', 'bin/mcp-bridge.mjs', 'bin/service-control.mjs',
|
|
13
|
+
'supervisor/supervise.mjs', 'supervisor/bridge-child.mjs',
|
|
14
|
+
'bin/upgrade-capability.json', 'ui/dist/index.html',
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
export function exactVersion(value) {
|
|
18
|
+
if (typeof value !== 'string' ||
|
|
19
|
+
semver.valid(value) !== value ||
|
|
20
|
+
semver.prerelease(value)) {
|
|
21
|
+
throw new Error('--to requires a canonical stable version, for example 2.1.0 (not a tag or range).');
|
|
22
|
+
}
|
|
23
|
+
return value;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function verifyArchive(bytes, integrity) {
|
|
27
|
+
if (!Buffer.isBuffer(bytes) ||
|
|
28
|
+
bytes.length === 0 ||
|
|
29
|
+
bytes.length > 32 * 1024 * 1024) throw new Error('Invalid package archive size.');
|
|
30
|
+
const match = /^sha512-([A-Za-z0-9+/]+={0,2})$/.exec(integrity ?? '');
|
|
31
|
+
if (!match) throw new Error('An exact SHA-512 package integrity is required.');
|
|
32
|
+
const actual = createHash('sha512').update(bytes).digest();
|
|
33
|
+
const expected = Buffer.from(match[1], 'base64');
|
|
34
|
+
if (actual.length !== expected.length ||
|
|
35
|
+
!timingSafeEqual(actual, expected)) throw new Error('Package archive integrity mismatch.');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function unpackUpgrade(bytes, version, destination, nodeVersion = process.versions.node) {
|
|
39
|
+
exactVersion(version);
|
|
40
|
+
const tar = gunzipSync(bytes, { maxOutputLength: 128 * 1024 * 1024 });
|
|
41
|
+
const files = new Map();
|
|
42
|
+
const seen = new Set();
|
|
43
|
+
let offset = 0;
|
|
44
|
+
let ended = false;
|
|
45
|
+
while (offset + 512 <= tar.length) {
|
|
46
|
+
const header = tar.subarray(offset, offset + 512);
|
|
47
|
+
if (header.every(value => value === 0)) {
|
|
48
|
+
if (!tar.subarray(offset).every(value => value === 0)) throw new Error('Data after archive terminator.');
|
|
49
|
+
ended = true;
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
const text = (start, size) => header.subarray(start, start + size).toString('utf8').replace(/\0.*$/s, '');
|
|
53
|
+
const octal = (start, size) => {
|
|
54
|
+
const value = text(start, size).trim();
|
|
55
|
+
if (!/^[0-7]+$/.test(value)) throw new Error('Invalid archive numeric field.');
|
|
56
|
+
return Number.parseInt(value, 8);
|
|
57
|
+
};
|
|
58
|
+
const checksum = [...header].reduce((sum, byte, index) => sum + (index >= 148 && index < 156 ? 32 : byte), 0);
|
|
59
|
+
if (octal(148, 8) !== checksum) throw new Error('Archive checksum mismatch.');
|
|
60
|
+
const prefix = text(345, 155);
|
|
61
|
+
const path = `${prefix ? `${prefix}/` : ''}${text(0, 100)}`;
|
|
62
|
+
const type = text(156, 1);
|
|
63
|
+
const size = octal(124, 12);
|
|
64
|
+
if (path === 'package/' &&
|
|
65
|
+
type === '5' &&
|
|
66
|
+
size === 0) {
|
|
67
|
+
offset += 512;
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (!['', '0', '5'].includes(type) ||
|
|
71
|
+
!/^package\/[A-Za-z0-9_./@+-]+$/.test(path)) throw new Error('Unsafe archive entry.');
|
|
72
|
+
const relative = path.slice(8).replace(/\/$/, '');
|
|
73
|
+
const parts = relative.split('/');
|
|
74
|
+
if (parts.some(part => !part || part === '.' || part === '..') ||
|
|
75
|
+
seen.has(relative.toLowerCase()) ||
|
|
76
|
+
/(^|\/)(?:node_modules|\.npmrc|\.git)(\/|$)/i.test(relative)) throw new Error('Unsafe or duplicate archive path.');
|
|
77
|
+
seen.add(relative.toLowerCase());
|
|
78
|
+
offset += 512;
|
|
79
|
+
if (offset + size > tar.length) throw new Error('Truncated archive.');
|
|
80
|
+
if (type !== '5') files.set(relative, { bytes: tar.subarray(offset, offset + size), mode: octal(100, 8) });
|
|
81
|
+
else if (size !== 0) throw new Error('Invalid archive directory.');
|
|
82
|
+
offset += Math.ceil(size / 512) * 512;
|
|
83
|
+
}
|
|
84
|
+
if (!ended) throw new Error('Missing archive terminator.');
|
|
85
|
+
const pkg = JSON.parse(files.get('package.json')?.bytes.toString('utf8') ?? 'null');
|
|
86
|
+
if (pkg?.name !== 'mcp-pacemaker' ||
|
|
87
|
+
pkg.version !== version ||
|
|
88
|
+
pkg.bin?.['mcp-pacemaker'] !== 'bin/cli.mjs' ||
|
|
89
|
+
pkg.bin?.['mcp-bridge'] !== 'bin/mcp-bridge.mjs' ||
|
|
90
|
+
typeof pkg.engines?.node !== 'string' ||
|
|
91
|
+
!semver.satisfies(nodeVersion, pkg.engines.node)) throw new Error('Package name, exact version or Node engine mismatch.');
|
|
92
|
+
for (const path of required) {
|
|
93
|
+
if (!files.get(path)?.bytes.length) throw new Error(`Target lacks managed upgrade capability: ${path}. No service was stopped.`);
|
|
94
|
+
}
|
|
95
|
+
const capability = JSON.parse(files.get('bin/upgrade-capability.json').bytes.toString('utf8'));
|
|
96
|
+
if (capability.protocol !== 1 ||
|
|
97
|
+
capability.portZero !== true ||
|
|
98
|
+
capability.admission !== true ||
|
|
99
|
+
capability.cli !== true) throw new Error('Target does not support managed readiness/admission/port allocation/CLI dispatch.');
|
|
100
|
+
if (capability.dependencyLock === 1 &&
|
|
101
|
+
!files.get('bin/dependency-lock.json')?.bytes.length) throw new Error('Target declares a bundled dependency lock but does not contain it.');
|
|
102
|
+
for (const file of files.values()) {
|
|
103
|
+
if (![0o644, 0o755].includes(file.mode)) throw new Error('Unexpected package file mode.');
|
|
104
|
+
}
|
|
105
|
+
for (const [path, file] of files) {
|
|
106
|
+
if (!path.endsWith('.exe')) continue;
|
|
107
|
+
if (file.bytes[0] !== 0x4d ||
|
|
108
|
+
file.bytes[1] !== 0x5a) throw new Error('Invalid packaged native binary.');
|
|
109
|
+
}
|
|
110
|
+
const native = ['bin/windows/PoolingSecurityHelper', 'bin/windows-lifetime/ProcessLifetimeHelper'];
|
|
111
|
+
if (capability.legacyRestartProtocol === 1) {
|
|
112
|
+
for (const path of [
|
|
113
|
+
'bin/legacy-upgrade.mjs', 'bin/legacy-installation.mjs', 'bin/legacy-task.mjs', 'bin/legacy-1.3.json',
|
|
114
|
+
'bin/windows-legacy-process.mjs', 'autostart/windows/legacy-task.ps1',
|
|
115
|
+
]) {
|
|
116
|
+
if (!files.get(path)?.bytes.length) throw new Error(`Target legacy recovery dependency is missing: ${path}.`);
|
|
117
|
+
}
|
|
118
|
+
native.push('bin/windows-legacy/LegacyProcessBroker');
|
|
119
|
+
}
|
|
120
|
+
if (capability.accountWorkerProtocol === 1) {
|
|
121
|
+
for (const path of [
|
|
122
|
+
'bin/account-upgrade-controller.mjs', 'bin/account-upgrade-worker.mjs', 'bin/account-worker-session.mjs',
|
|
123
|
+
'bin/account-task-adapter.mjs', 'bin/task-only-controller.mjs', 'bin/task-scope.mjs',
|
|
124
|
+
'bin/task-transaction-protocol.mjs', 'bin/windows-task-channel.mjs', 'autostart/windows/account-task.ps1',
|
|
125
|
+
'autostart/windows/inspect-worker-process-sessions.ps1',
|
|
126
|
+
]) {
|
|
127
|
+
if (!files.get(path)?.bytes.length) throw new Error(`Target account worker dependency is missing: ${path}.`);
|
|
128
|
+
}
|
|
129
|
+
native.push('bin/windows-task-channel/TaskChannelGuard');
|
|
130
|
+
}
|
|
131
|
+
for (const base of native) {
|
|
132
|
+
const binary = files.get(`${base}.exe`);
|
|
133
|
+
const metadata = files.get(`${base}.build.json`);
|
|
134
|
+
if (!binary ||
|
|
135
|
+
!metadata) throw new Error('Missing packaged native binary identity.');
|
|
136
|
+
const build = JSON.parse(metadata.bytes.toString('utf8'));
|
|
137
|
+
if (createHash('sha256').update(binary.bytes).digest('hex') !== build.binarySha256) {
|
|
138
|
+
throw new Error('Packaged native binary hash mismatch.');
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (readdirSync(destination).length) throw new Error('Package destination is not empty.');
|
|
142
|
+
for (const [path, file] of files) {
|
|
143
|
+
const output = join(destination, path);
|
|
144
|
+
mkdirSync(dirname(output), { recursive: true, mode: 0o700 });
|
|
145
|
+
writeFileSync(output, file.bytes, { flag: 'wx', mode: file.mode });
|
|
146
|
+
}
|
|
147
|
+
return { version, files: [...files].map(([path, file]) => ({
|
|
148
|
+
path, sha256: createHash('sha256').update(file.bytes).digest('hex'),
|
|
149
|
+
})) };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export async function acquireUpgrade(version, directory, { registry, run = runFile } = {}) {
|
|
153
|
+
exactVersion(version);
|
|
154
|
+
registry = validateRegistry(registry);
|
|
155
|
+
const cache = join(directory, 'cache');
|
|
156
|
+
const metadata = await readNpmMetadata(`mcp-pacemaker@${version}`, undefined, { registry, cache, run });
|
|
157
|
+
if (metadata.name !== 'mcp-pacemaker' ||
|
|
158
|
+
metadata.version !== version ||
|
|
159
|
+
metadata.deprecated) throw new Error('Exact target metadata was not verified.');
|
|
160
|
+
const args = [npmCliPath(), 'pack', `mcp-pacemaker@${version}`, '--json', '--ignore-scripts',
|
|
161
|
+
'--pack-destination', directory, '--cache', cache];
|
|
162
|
+
if (registry !== undefined) args.push('--registry', registry);
|
|
163
|
+
let stdout;
|
|
164
|
+
try {
|
|
165
|
+
({ stdout } = await run(process.execPath, args, {
|
|
166
|
+
cwd: process.cwd(), encoding: 'utf8', windowsHide: true, timeout: 120000, maxBuffer: 4 * 1024 * 1024,
|
|
167
|
+
}));
|
|
168
|
+
} catch {
|
|
169
|
+
throw new Error('Package acquisition failed under the configured npm policy. No registry or policy fallback was attempted.');
|
|
170
|
+
}
|
|
171
|
+
const result = normalizePackResult(JSON.parse(stdout), version);
|
|
172
|
+
const bytes = readFileSync(join(directory, result.filename));
|
|
173
|
+
verifyArchive(bytes, metadata.dist?.integrity);
|
|
174
|
+
return { bytes, integrity: metadata.dist.integrity };
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function normalizePackResult(value, version) {
|
|
178
|
+
let result;
|
|
179
|
+
if (Array.isArray(value) &&
|
|
180
|
+
value.length === 1) result = value[0];
|
|
181
|
+
else if (value &&
|
|
182
|
+
typeof value === 'object' &&
|
|
183
|
+
Object.keys(value).length === 1 &&
|
|
184
|
+
Object.hasOwn(value, 'mcp-pacemaker')) result = value['mcp-pacemaker'];
|
|
185
|
+
if (!result ||
|
|
186
|
+
Array.isArray(result) ||
|
|
187
|
+
result.name !== 'mcp-pacemaker' ||
|
|
188
|
+
result.version !== version ||
|
|
189
|
+
result.filename !== `mcp-pacemaker-${version}.tgz`) throw new Error('Unexpected npm pack result.');
|
|
190
|
+
return result;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export async function configuredRegistry(registry) {
|
|
194
|
+
if (registry !== undefined) return validateRegistry(registry);
|
|
195
|
+
try {
|
|
196
|
+
const { stdout } = await runFile(process.execPath, [
|
|
197
|
+
npmCliPath(), 'config', 'get', 'registry', '--logs-max=0', '--update-notifier=false',
|
|
198
|
+
], {
|
|
199
|
+
encoding: 'utf8', windowsHide: true, timeout: 10000, maxBuffer: 16384,
|
|
200
|
+
});
|
|
201
|
+
return validateRegistry(stdout.trim());
|
|
202
|
+
} catch {
|
|
203
|
+
throw new Error('Effective npm registry could not be read. Set --registry explicitly; no fallback selected.');
|
|
204
|
+
}
|
|
205
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"binary": "LegacyProcessBroker.exe",
|
|
4
|
+
"binarySha256": "64323058e9c0437409522b6cca6d1a47c32e125980b83a07021932d1d5a317ef",
|
|
5
|
+
"inputs": {
|
|
6
|
+
"targetFramework": ".NETFramework,Version=v4.6.2",
|
|
7
|
+
"platform": "AnyCPU",
|
|
8
|
+
"compilerVersion": "5.9.0-1.26357.3 (35d9211b841e7613c1d2f8f5af6d628ace696c4c)",
|
|
9
|
+
"compilerSha256": "3aafb7b9c54fa31a7092af35148971ee616965e7f9a0b80fb7fdc4bdd1d1a555",
|
|
10
|
+
"compilerArguments": [
|
|
11
|
+
"/nologo",
|
|
12
|
+
"/noconfig",
|
|
13
|
+
"/nostdlib+",
|
|
14
|
+
"/target:exe",
|
|
15
|
+
"/platform:anycpu",
|
|
16
|
+
"/langversion:7.3",
|
|
17
|
+
"/optimize+",
|
|
18
|
+
"/debug-",
|
|
19
|
+
"/deterministic+",
|
|
20
|
+
"/checked-",
|
|
21
|
+
"/unsafe-",
|
|
22
|
+
"/warn:4",
|
|
23
|
+
"/warnaserror+",
|
|
24
|
+
"/utf8output"
|
|
25
|
+
],
|
|
26
|
+
"pathMap": "/_/mcp-pacemaker/windows-legacy",
|
|
27
|
+
"sourceEncoding": "UTF-8 without BOM, LF line endings",
|
|
28
|
+
"sourceSha256": {
|
|
29
|
+
"AssemblyInfo.cs": "09f672607b66aff39c457899d270688972e440aafaa43b2cb56c1a2367576356",
|
|
30
|
+
"LegacyNative.cs": "28f496cba02ebfbbe07102bc70133198b40846838c0189e0bfcdb91d2e0a95c6",
|
|
31
|
+
"LegacyProcessBroker.cs": "2edd5f77787498d5323185e00b663084e140c9aec6ebca3921d66078bbc66c2a"
|
|
32
|
+
},
|
|
33
|
+
"buildScriptSha256": "2ffa31155f6aa8f6b4239facb0f062df167a01ac7a7ce6d9ae0be60e60fd0201",
|
|
34
|
+
"referenceSha256": {
|
|
35
|
+
"mscorlib.dll": "7118fe658567606fa0538f7b5a2071e4606756c07e9062e1e5b24fa49e97d1f6",
|
|
36
|
+
"System.dll": "b76822f9c08aff597120e3167b3e0fbda836d6bc6f14bc4bc2eeffab5b2c6125",
|
|
37
|
+
"System.Core.dll": "774e965a6c25891a96d6d3b838ae10226ed9963a73b09e4be7c2ec275ffb5886",
|
|
38
|
+
"System.Management.dll": "bc35cddea6226db4d428a9fb70fcefe321a2af003c947edb812f3e911f8e4094",
|
|
39
|
+
"System.Web.Extensions.dll": "7a32bf3263aebed93c3a0847294aebdde15f7427541ed223a4cc4eb93c36d556"
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
using System.Reflection;
|
|
2
|
+
using System.Runtime.InteropServices;
|
|
3
|
+
|
|
4
|
+
[assembly: AssemblyTitle("MCP Legacy Process Broker")]
|
|
5
|
+
[assembly: AssemblyDescription("Verified held-identity stop of a selected legacy observed process set")]
|
|
6
|
+
[assembly: AssemblyVersion("1.0.0.0")]
|
|
7
|
+
[assembly: AssemblyFileVersion("1.0.0.0")]
|
|
8
|
+
[assembly: ComVisible(false)]
|