mecord-connect 1.0.0

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.
Files changed (82) hide show
  1. package/DISCLOSURE +47 -0
  2. package/LICENSE +64 -0
  3. package/README.md +44 -0
  4. package/bin/operator.mjs +10 -0
  5. package/package.json +58 -0
  6. package/runtime/app/apps/local-agent/src/approval-store.js +342 -0
  7. package/runtime/app/apps/local-agent/src/bootstrap-config.js +255 -0
  8. package/runtime/app/apps/local-agent/src/cli.js +237 -0
  9. package/runtime/app/apps/local-agent/src/device-reset.js +260 -0
  10. package/runtime/app/apps/local-agent/src/emergency-stop.js +141 -0
  11. package/runtime/app/apps/local-agent/src/main.js +311 -0
  12. package/runtime/app/apps/local-agent/src/privacy-data.js +129 -0
  13. package/runtime/app/apps/local-agent/src/relay-agent.js +333 -0
  14. package/runtime/app/apps/local-agent/src/relay-enrollment.js +156 -0
  15. package/runtime/app/apps/local-agent/src/relay-session-credentials.js +436 -0
  16. package/runtime/app/apps/local-agent/src/remote.js +130 -0
  17. package/runtime/app/apps/local-agent/src/runtime-factory.js +66 -0
  18. package/runtime/app/apps/local-agent/src/server.js +578 -0
  19. package/runtime/app/package.json +4 -0
  20. package/runtime/app/src/capabilities/browser-cdp-connection.js +199 -0
  21. package/runtime/app/src/capabilities/browser-cdp-frames.js +288 -0
  22. package/runtime/app/src/capabilities/browser-cdp-page.js +430 -0
  23. package/runtime/app/src/capabilities/browser-cdp.js +384 -0
  24. package/runtime/app/src/capabilities/browser-managed.js +345 -0
  25. package/runtime/app/src/capabilities/docker.js +448 -0
  26. package/runtime/app/src/capabilities/filesystem.js +264 -0
  27. package/runtime/app/src/capabilities/git-checkpoint.js +415 -0
  28. package/runtime/app/src/capabilities/git-write.js +321 -0
  29. package/runtime/app/src/capabilities/git.js +252 -0
  30. package/runtime/app/src/capabilities/index.js +16 -0
  31. package/runtime/app/src/capabilities/path-scope.js +86 -0
  32. package/runtime/app/src/capabilities/postgres.js +566 -0
  33. package/runtime/app/src/capabilities/process.js +258 -0
  34. package/runtime/app/src/capabilities/project-command.js +599 -0
  35. package/runtime/app/src/capabilities/project-transaction.js +215 -0
  36. package/runtime/app/src/capabilities/project.js +67 -0
  37. package/runtime/app/src/capabilities/system.js +40 -0
  38. package/runtime/app/src/capabilities/vscode.js +312 -0
  39. package/runtime/app/src/capabilities/windows-uia.js +331 -0
  40. package/runtime/app/src/core/account-device-registry.js +604 -0
  41. package/runtime/app/src/core/action-identity.js +44 -0
  42. package/runtime/app/src/core/audit.js +295 -0
  43. package/runtime/app/src/core/capability-policy.js +58 -0
  44. package/runtime/app/src/core/child-environment.js +29 -0
  45. package/runtime/app/src/core/device-enrollment.js +417 -0
  46. package/runtime/app/src/core/device-identity.js +436 -0
  47. package/runtime/app/src/core/device-registry.js +477 -0
  48. package/runtime/app/src/core/device-reset.js +178 -0
  49. package/runtime/app/src/core/device-routing.js +305 -0
  50. package/runtime/app/src/core/durable-state.js +306 -0
  51. package/runtime/app/src/core/errors.js +20 -0
  52. package/runtime/app/src/core/evidence.js +9 -0
  53. package/runtime/app/src/core/index.js +17 -0
  54. package/runtime/app/src/core/machine-state.js +160 -0
  55. package/runtime/app/src/core/network-authority.js +20 -0
  56. package/runtime/app/src/core/policy.js +67 -0
  57. package/runtime/app/src/core/product-identity.js +7 -0
  58. package/runtime/app/src/core/provenance.js +18 -0
  59. package/runtime/app/src/core/provider-learning.js +274 -0
  60. package/runtime/app/src/core/public-plugin-surface.js +25 -0
  61. package/runtime/app/src/core/public-restricted-data.js +105 -0
  62. package/runtime/app/src/core/rate-limit.js +118 -0
  63. package/runtime/app/src/core/relay-client.js +568 -0
  64. package/runtime/app/src/core/relay-delivery-store.js +512 -0
  65. package/runtime/app/src/core/relay-result-store.js +321 -0
  66. package/runtime/app/src/core/release-update.js +253 -0
  67. package/runtime/app/src/core/router.js +96 -0
  68. package/runtime/app/src/core/runtime.js +136 -0
  69. package/runtime/app/src/core/scoped-path-syntax.js +16 -0
  70. package/runtime/app/src/core/session-token.js +498 -0
  71. package/runtime/app/src/core/task-orchestrator.js +1148 -0
  72. package/runtime/app/src/core/task-store.js +517 -0
  73. package/runtime/app/src/core/task.js +142 -0
  74. package/runtime/app/src/core/trusted-executable.js +124 -0
  75. package/runtime/app/src/core/types.js +83 -0
  76. package/runtime/app/src/core/windows-path-lease.js +138 -0
  77. package/runtime/native/operator-windows-dpapi.exe +0 -0
  78. package/runtime/native/operator-windows-path-lease.exe +0 -0
  79. package/runtime/native/operator-windows-uia.exe +0 -0
  80. package/runtime/runtime-manifest.json +380 -0
  81. package/src/cli.mjs +345 -0
  82. package/src/publish-check.mjs +22 -0
package/src/cli.mjs ADDED
@@ -0,0 +1,345 @@
1
+ import crypto from 'node:crypto';
2
+ import { spawn } from 'node:child_process';
3
+ import { createReadStream } from 'node:fs';
4
+ import fs from 'node:fs/promises';
5
+ import path from 'node:path';
6
+ import { fileURLToPath } from 'node:url';
7
+
8
+ const PACKAGE_NAME = 'mecord-connect';
9
+ const RELAY_URL = 'wss://operator.splcart.in/device';
10
+ const RELAY_RESULT_URL = 'https://operator.splcart.in/v1/device-result';
11
+ const PAIR_URL_BASE = 'https://auth.splcart.in/pair';
12
+ const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
13
+ const runtimeRoot = path.join(packageRoot, 'runtime');
14
+ const manifestPath = path.join(runtimeRoot, 'runtime-manifest.json');
15
+ const remoteEntrypoint = path.join(runtimeRoot, 'app', 'apps', 'local-agent', 'src', 'remote.js');
16
+ const helperPath = (name) => path.join(runtimeRoot, 'native', name);
17
+
18
+ const DESKTOP_ENV_KEYS = [
19
+ 'PATH', 'Path', 'PATHEXT', 'SYSTEMROOT', 'WINDIR',
20
+ 'HOME', 'USERPROFILE', 'HOMEDRIVE', 'HOMEPATH',
21
+ 'USER', 'USERNAME', 'LOGNAME', 'TMP', 'TEMP', 'TMPDIR',
22
+ 'LANG', 'LC_ALL', 'LC_CTYPE', 'TERM', 'COLORTERM', 'SHELL',
23
+ 'PROGRAMFILES', 'PROGRAMFILES(X86)', 'PROGRAMW6432', 'PROGRAMDATA',
24
+ 'LOCALAPPDATA', 'APPDATA', 'DISPLAY', 'WAYLAND_DISPLAY',
25
+ 'DBUS_SESSION_BUS_ADDRESS'
26
+ ];
27
+ const WINDOWS_NATIVE_ENV_KEYS = [
28
+ 'SYSTEMROOT', 'WINDIR', 'USERPROFILE', 'LOCALAPPDATA', 'APPDATA', 'TMP', 'TEMP'
29
+ ];
30
+ const REQUIRED_RUNTIME_FILES = [
31
+ 'app/apps/local-agent/src/remote.js',
32
+ 'native/operator-windows-dpapi.exe',
33
+ 'native/operator-windows-uia.exe',
34
+ 'native/operator-windows-path-lease.exe'
35
+ ];
36
+
37
+ function usage() {
38
+ return `Mecord Connect
39
+
40
+ Usage:
41
+ npx mecord-connect@latest remote [--root <folder>] [--no-browser]
42
+ npx mecord-connect@latest doctor
43
+ npx mecord-connect@latest --help
44
+
45
+ remote starts the local policy/runtime agent and its secure relay connection only.
46
+ ChatGPT connects to the hosted MCP service; no local MCP server or MSIX install is required.
47
+ --no-browser disables automatic managed-Chromium launch for private browser tasks;
48
+ the secure authenticated pairing URL is still printed when pairing is required.`;
49
+ }
50
+
51
+ export function parseArgs(argv) {
52
+ if (argv.length === 0 || argv[0] === '--help' || argv[0] === '-h' || argv[0] === 'help') {
53
+ return { command: 'help' };
54
+ }
55
+ if (argv[0] === 'doctor') {
56
+ if (argv.length !== 1) throw new Error('doctor does not accept arguments.');
57
+ return { command: 'doctor' };
58
+ }
59
+ if (argv[0] !== 'remote') throw new Error(`Unknown command '${argv[0]}'. Use --help.`);
60
+ if (argv.length === 2 && (argv[1] === '--help' || argv[1] === '-h')) return { command: 'help' };
61
+ let root = process.cwd();
62
+ let browser = true;
63
+ for (let index = 1; index < argv.length; index += 1) {
64
+ const arg = argv[index];
65
+ if (arg === '--no-browser') {
66
+ browser = false;
67
+ continue;
68
+ }
69
+ if (arg === '--root') {
70
+ const value = argv[++index];
71
+ if (!value) throw new Error('--root requires a folder.');
72
+ root = value;
73
+ continue;
74
+ }
75
+ throw new Error(`Unknown remote option '${arg}'.`);
76
+ }
77
+ return { command: 'remote', root, browser };
78
+ }
79
+
80
+ export function assertSupportedRuntime(
81
+ platform = process.platform,
82
+ arch = process.arch,
83
+ version = process.versions.node
84
+ ) {
85
+ if (platform !== 'win32' || arch !== 'x64') {
86
+ throw new Error(`Mecord Connect remote currently requires Windows x64; received ${platform} ${arch}.`);
87
+ }
88
+ const parts = String(version).split('.').map(Number);
89
+ if (parts.length < 2 || parts.some((part) => !Number.isSafeInteger(part))) {
90
+ throw new Error('Unable to determine the Node.js version.');
91
+ }
92
+ const major = parts[0];
93
+ const supported = (major === 22 && parts[1] >= 14) || major === 24 || major === 26;
94
+ if (!supported) {
95
+ throw new Error(`Mecord Connect remote requires Node.js 22.14+, 24.x, or 26.x; received ${version}.`);
96
+ }
97
+ }
98
+
99
+ function pickEnvironment(keys, source) {
100
+ const env = {};
101
+ for (const key of keys) {
102
+ if (source[key] !== undefined) env[key] = source[key];
103
+ }
104
+ return env;
105
+ }
106
+
107
+ export function safeRuntimeEnvironment(source = process.env) {
108
+ return pickEnvironment(DESKTOP_ENV_KEYS, source);
109
+ }
110
+
111
+ function nativeEnvironment(source = process.env) {
112
+ return pickEnvironment(WINDOWS_NATIVE_ENV_KEYS, source);
113
+ }
114
+
115
+ async function sha256(file) {
116
+ const hash = crypto.createHash('sha256');
117
+ for await (const chunk of createReadStream(file)) hash.update(chunk);
118
+ return hash.digest('hex');
119
+ }
120
+
121
+ function validRuntimeRelativePath(input) {
122
+ const value = String(input ?? '');
123
+ if (!value || value.includes('\\') || path.posix.isAbsolute(value) || path.posix.normalize(value) !== value) {
124
+ throw new Error('Runtime manifest contains an unsafe file path.');
125
+ }
126
+ if (value === '..' || value.startsWith('../') || value.includes('/../')) {
127
+ throw new Error('Runtime manifest contains an unsafe file path.');
128
+ }
129
+ return value;
130
+ }
131
+
132
+ export function validateRuntimeManifest(input) {
133
+ if (!input || typeof input !== 'object' || Array.isArray(input)) throw new Error('Runtime manifest must be an object.');
134
+ if (input.schemaVersion !== 1 || input.package !== PACKAGE_NAME) throw new Error('Runtime manifest identity is invalid.');
135
+ if (input.platform !== 'win32' || input.arch !== 'x64') throw new Error('Runtime manifest platform is invalid.');
136
+ if (!/^\d+\.\d+\.\d+$/.test(String(input.version ?? ''))) throw new Error('Runtime manifest version is invalid.');
137
+ if (!/^[0-9a-f]{40}$/i.test(String(input.sourceCommit ?? ''))) throw new Error('Runtime manifest source commit is invalid.');
138
+ if (!Array.isArray(input.files) || input.files.length < REQUIRED_RUNTIME_FILES.length || input.files.length > 4096) {
139
+ throw new Error('Runtime manifest file list is invalid.');
140
+ }
141
+ const seen = new Set();
142
+ const files = input.files.map((entry) => {
143
+ if (!entry || typeof entry !== 'object' || Array.isArray(entry)) throw new Error('Runtime manifest file entry is invalid.');
144
+ const relativePath = validRuntimeRelativePath(entry.path);
145
+ const sizeBytes = Number(entry.sizeBytes);
146
+ const digest = String(entry.sha256 ?? '').toLowerCase();
147
+ if (seen.has(relativePath)) throw new Error(`Runtime manifest contains duplicate path ${relativePath}.`);
148
+ if (!Number.isSafeInteger(sizeBytes) || sizeBytes < 1 || sizeBytes > 64 * 1024 * 1024) {
149
+ throw new Error(`Runtime manifest size is invalid for ${relativePath}.`);
150
+ }
151
+ if (!/^[0-9a-f]{64}$/.test(digest)) throw new Error(`Runtime manifest SHA-256 is invalid for ${relativePath}.`);
152
+ seen.add(relativePath);
153
+ return { path: relativePath, sizeBytes, sha256: digest };
154
+ });
155
+ for (const required of REQUIRED_RUNTIME_FILES) {
156
+ if (!seen.has(required)) throw new Error(`Runtime manifest is missing ${required}.`);
157
+ }
158
+ return {
159
+ schemaVersion: 1,
160
+ package: PACKAGE_NAME,
161
+ version: String(input.version),
162
+ platform: 'win32',
163
+ arch: 'x64',
164
+ sourceCommit: String(input.sourceCommit).toLowerCase(),
165
+ files
166
+ };
167
+ }
168
+
169
+ async function listRuntimeFiles(root, relative = '') {
170
+ const directory = path.join(root, ...relative.split('/').filter(Boolean));
171
+ const entries = await fs.readdir(directory, { withFileTypes: true });
172
+ const files = [];
173
+ for (const entry of entries) {
174
+ const next = relative ? `${relative}/${entry.name}` : entry.name;
175
+ if (next === 'runtime-manifest.json') continue;
176
+ const full = path.join(root, ...next.split('/'));
177
+ const stat = await fs.lstat(full);
178
+ if (stat.isSymbolicLink()) throw new Error(`Runtime payload contains a symbolic link: ${next}.`);
179
+ if (stat.isDirectory()) files.push(...await listRuntimeFiles(root, next));
180
+ else if (stat.isFile()) files.push(next);
181
+ else throw new Error(`Runtime payload contains an unsupported filesystem entry: ${next}.`);
182
+ }
183
+ return files;
184
+ }
185
+
186
+ export async function verifyRuntimePayload() {
187
+ const raw = JSON.parse(await fs.readFile(manifestPath, 'utf8'));
188
+ const manifest = validateRuntimeManifest(raw);
189
+ const actualPaths = (await listRuntimeFiles(runtimeRoot)).sort();
190
+ const expectedPaths = manifest.files.map((entry) => entry.path).sort();
191
+ if (actualPaths.length !== expectedPaths.length || actualPaths.some((value, index) => value !== expectedPaths[index])) {
192
+ throw new Error('Runtime payload file set does not match its signed-in-package manifest.');
193
+ }
194
+ for (const entry of manifest.files) {
195
+ const file = path.join(runtimeRoot, ...entry.path.split('/'));
196
+ const stat = await fs.stat(file);
197
+ if (!stat.isFile() || stat.size !== entry.sizeBytes) throw new Error(`Runtime payload size mismatch: ${entry.path}.`);
198
+ if (await sha256(file) !== entry.sha256) throw new Error(`Runtime payload SHA-256 mismatch: ${entry.path}.`);
199
+ }
200
+ return manifest;
201
+ }
202
+
203
+ export function assertSerializableAuthorizedRoot(root) {
204
+ const value = String(root ?? '');
205
+ if (value.includes(path.win32.delimiter)) {
206
+ throw new Error(`Authorized root cannot contain the Windows path-list delimiter '${path.win32.delimiter}'.`);
207
+ }
208
+ return value;
209
+ }
210
+
211
+ async function canonicalDirectory(input) {
212
+ const resolved = path.resolve(input);
213
+ const stat = await fs.stat(resolved).catch(() => null);
214
+ if (!stat?.isDirectory()) throw new Error(`Authorized root is not a directory: ${resolved}`);
215
+ return assertSerializableAuthorizedRoot(await fs.realpath(resolved));
216
+ }
217
+
218
+ function randomSecret() {
219
+ return crypto.randomBytes(32).toString('base64url');
220
+ }
221
+
222
+ function runChild(executable, args, options = {}) {
223
+ return new Promise((resolve, reject) => {
224
+ const child = spawn(executable, args, { shell: false, ...options });
225
+ child.once('error', reject);
226
+ child.once('close', (code, signal) => {
227
+ if (signal) reject(new Error(`Process terminated by signal ${signal}.`));
228
+ else resolve(code ?? 1);
229
+ });
230
+ });
231
+ }
232
+
233
+ async function selfTestHelper(name) {
234
+ const file = helperPath(name);
235
+ const code = await runChild(file, ['--self-test'], {
236
+ env: nativeEnvironment(),
237
+ windowsHide: true,
238
+ stdio: ['ignore', 'ignore', 'pipe']
239
+ });
240
+ if (code !== 0) throw new Error(`${name} self-test failed with exit code ${code}.`);
241
+ }
242
+
243
+ async function healthCheckUia() {
244
+ const file = helperPath('operator-windows-uia.exe');
245
+ await new Promise((resolve, reject) => {
246
+ const child = spawn(file, [], {
247
+ shell: false,
248
+ windowsHide: true,
249
+ env: nativeEnvironment(),
250
+ stdio: ['pipe', 'pipe', 'pipe']
251
+ });
252
+ let settled = false;
253
+ let output = '';
254
+ const finish = (error) => {
255
+ if (settled) return;
256
+ settled = true;
257
+ clearTimeout(timer);
258
+ try { child.stdin.end(); } catch { /* noop */ }
259
+ try { child.kill(); } catch { /* noop */ }
260
+ if (error) reject(error); else resolve();
261
+ };
262
+ child.once('error', () => finish(new Error('Windows UIA helper could not start.')));
263
+ child.stdout.setEncoding('utf8');
264
+ child.stdout.on('data', (chunk) => {
265
+ output += chunk;
266
+ if (Buffer.byteLength(output, 'utf8') > 64 * 1024) {
267
+ finish(new Error('Windows UIA helper returned oversized health output.'));
268
+ return;
269
+ }
270
+ const newline = output.indexOf('\n');
271
+ if (newline < 0) return;
272
+ try {
273
+ const response = JSON.parse(output.slice(0, newline));
274
+ if (response?.id !== 'operator-doctor' || response?.ok !== true) throw new Error('unexpected response');
275
+ finish();
276
+ } catch {
277
+ finish(new Error('Windows UIA helper health response was invalid.'));
278
+ }
279
+ });
280
+ const timer = setTimeout(() => finish(new Error('Windows UIA helper health check timed out.')), 5000);
281
+ timer.unref();
282
+ child.stdin.end(`${JSON.stringify({ id: 'operator-doctor', method: 'health', params: {} })}\n`);
283
+ });
284
+ }
285
+
286
+ export async function doctor() {
287
+ assertSupportedRuntime();
288
+ const manifest = await verifyRuntimePayload();
289
+ await selfTestHelper('operator-windows-dpapi.exe');
290
+ await selfTestHelper('operator-windows-path-lease.exe');
291
+ await healthCheckUia();
292
+ console.log('Mecord Connect doctor: PASS');
293
+ console.log(`Package: ${PACKAGE_NAME}@${manifest.version}`);
294
+ console.log(`Source: ${manifest.sourceCommit}`);
295
+ console.log(`Runtime files: ${manifest.files.length}`);
296
+ console.log(`Relay authority: ${RELAY_URL}`);
297
+ return manifest;
298
+ }
299
+
300
+ export async function runRemote({ root = process.cwd(), browser = true } = {}) {
301
+ assertSupportedRuntime();
302
+ await verifyRuntimePayload();
303
+ const authorizedRoot = await canonicalDirectory(root);
304
+ const env = safeRuntimeEnvironment();
305
+ env.OPERATOR_ALLOWED_ROOTS = authorizedRoot;
306
+ env.OPERATOR_AGENT_HOST = '127.0.0.1';
307
+ env.OPERATOR_AGENT_PORT = '0';
308
+ env.OPERATOR_AGENT_TOKEN = randomSecret();
309
+ env.OPERATOR_RECOVERY_TOKEN = randomSecret();
310
+ env.OPERATOR_RELAY_URL = RELAY_URL;
311
+ env.OPERATOR_RELAY_RESULT_URL = RELAY_RESULT_URL;
312
+ env.OPERATOR_RELAY_REQUIRED = '1';
313
+ env.OPERATOR_PAIR_URL_BASE = PAIR_URL_BASE;
314
+ env.OPERATOR_BROWSER_AUTO_LAUNCH = browser ? '1' : '0';
315
+ env.OPERATOR_WINDOWS_DPAPI_PATH = helperPath('operator-windows-dpapi.exe');
316
+ env.OPERATOR_WINDOWS_UIA_PATH = helperPath('operator-windows-uia.exe');
317
+ env.OPERATOR_WINDOWS_PATH_LEASE_PATH = helperPath('operator-windows-path-lease.exe');
318
+ env.OPERATOR_REMOTE_PACKAGE = PACKAGE_NAME;
319
+
320
+ console.log('[mecord-connect] starting secure remote runtime');
321
+ console.log(`[mecord-connect] authorized root: ${authorizedRoot}`);
322
+ console.log('[mecord-connect] ChatGPT uses the hosted MCP edge; no local MCP server is started.');
323
+ console.log('[mecord-connect] if this device is not paired yet, open the secure pairing link shown below and confirm the device in your Mecord account.');
324
+
325
+ const code = await runChild(process.execPath, [remoteEntrypoint], {
326
+ cwd: authorizedRoot,
327
+ env,
328
+ stdio: 'inherit',
329
+ windowsHide: false
330
+ });
331
+ if (code !== 0) throw new Error(`Mecord Connect remote runtime exited with code ${code}.`);
332
+ }
333
+
334
+ export async function main(argv) {
335
+ const args = parseArgs(argv);
336
+ if (args.command === 'help') {
337
+ console.log(usage());
338
+ return;
339
+ }
340
+ if (args.command === 'doctor') {
341
+ await doctor();
342
+ return;
343
+ }
344
+ await runRemote({ root: args.root, browser: args.browser });
345
+ }
@@ -0,0 +1,22 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { verifyRuntimePayload } from './cli.mjs';
5
+
6
+ const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
7
+ const pkg = JSON.parse(await fs.readFile(path.join(packageRoot, 'package.json'), 'utf8'));
8
+ if (pkg.name !== 'mecord-connect') throw new Error('Unexpected npm package name.');
9
+ if (!/^\d+\.\d+\.\d+$/.test(pkg.version)) throw new Error('Mecord Connect npm version must use stable semantic versioning.');
10
+
11
+ const manifest = await verifyRuntimePayload();
12
+ if (manifest.version !== pkg.version) {
13
+ throw new Error(`Runtime manifest version ${manifest.version} does not match package ${pkg.version}.`);
14
+ }
15
+ const expectedCommit = String(process.env.OPERATOR_SOURCE_COMMIT || '').trim().toLowerCase();
16
+ if (!/^[0-9a-f]{40}$/.test(expectedCommit)) {
17
+ throw new Error('Refusing npm publish: OPERATOR_SOURCE_COMMIT must be the exact 40-character publication commit.');
18
+ }
19
+ if (manifest.sourceCommit !== expectedCommit) {
20
+ throw new Error(`Runtime payload source ${manifest.sourceCommit} does not match publication source ${expectedCommit}.`);
21
+ }
22
+ console.log(`mecord-connect publish check: PASS (${pkg.version}, source ${manifest.sourceCommit}, ${manifest.files.length} runtime files)`);