knosky 0.6.3 → 0.7.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 (64) hide show
  1. package/CHANGELOG.md +149 -93
  2. package/CREDITS.md +14 -14
  3. package/LICENSE.md +76 -76
  4. package/LIMITATIONS.md +33 -23
  5. package/PRIVACY.md +30 -30
  6. package/README.md +170 -117
  7. package/SECURITY.md +78 -46
  8. package/action/post-comment.mjs +94 -89
  9. package/action.yml +62 -62
  10. package/bin/knosky.mjs +279 -105
  11. package/core/CONTRACT.md +70 -70
  12. package/core/append-only-checkpoint.mjs +215 -0
  13. package/core/audit-writer.mjs +317 -0
  14. package/core/benchmark-results.mjs +225 -225
  15. package/core/bundle.mjs +178 -178
  16. package/core/churn.mjs +23 -23
  17. package/core/ci.mjs +268 -268
  18. package/core/comparison.mjs +189 -189
  19. package/core/config.mjs +189 -189
  20. package/core/constants.mjs +13 -13
  21. package/core/contract.mjs +123 -123
  22. package/core/cross-repo.mjs +111 -111
  23. package/core/decision-codes.mjs +92 -0
  24. package/core/destination.mjs +161 -161
  25. package/core/district-classification.mjs +111 -0
  26. package/core/doctor-scorecard.mjs +369 -0
  27. package/core/domain-store.mjs +347 -0
  28. package/core/edges.mjs +43 -43
  29. package/core/escalate.mjs +68 -68
  30. package/core/freshness.mjs +198 -194
  31. package/core/fs-indexer.mjs +218 -218
  32. package/core/key-store.mjs +348 -348
  33. package/core/layout.mjs +46 -46
  34. package/core/ledger.mjs +176 -141
  35. package/core/local-ipc-identity.mjs +500 -0
  36. package/core/lod.mjs +155 -155
  37. package/core/mode-b.mjs +410 -0
  38. package/core/multi-model-benchmark.mjs +405 -405
  39. package/core/net-lockdown.mjs +421 -0
  40. package/core/onboarding.mjs +223 -223
  41. package/core/operator-auth.mjs +317 -0
  42. package/core/overlays.mjs +45 -45
  43. package/core/policy-lattice.mjs +142 -0
  44. package/core/pr-comment.mjs +198 -198
  45. package/core/protocol-spec.mjs +460 -460
  46. package/core/provenance.mjs +320 -0
  47. package/core/retrieve.mjs +63 -63
  48. package/core/route.mjs +304 -304
  49. package/core/schema.mjs +275 -275
  50. package/core/signing-tiers.mjs +1265 -0
  51. package/core/swarm-bench.mjs +106 -0
  52. package/core/swarm-coordinator.mjs +867 -0
  53. package/core/trust-root-rekey.mjs +410 -0
  54. package/mcp/server.mjs +264 -108
  55. package/package.json +56 -46
  56. package/renderer/art/kenney/buildingTiles_sheet.xml +130 -130
  57. package/renderer/art/kenney/cityDetails_sheet.xml +12 -12
  58. package/renderer/art/kenney/landscapeTiles_sheet.xml +129 -129
  59. package/renderer/art/kenney/sheet_allCars.xml +545 -545
  60. package/renderer/build-rich.mjs +43 -43
  61. package/renderer/city.template.html +808 -808
  62. package/ssot/decision-codes.json +133 -0
  63. package/ssot/ladder-l0-l3.md +232 -0
  64. package/ssot/tool-menu.json +130 -0
@@ -0,0 +1,500 @@
1
+ // KnoSky F0.4 — Local authentication: kernel-verified principal identity (SAT-548).
2
+ //
3
+ // Establishes the OS-kernel-verified identity of the process on the other end of a
4
+ // local IPC connection. NEVER relies on a self-asserted field from the request
5
+ // payload — identity comes exclusively from the kernel via:
6
+ //
7
+ // Linux : SO_PEERCRED (getsockopt over a Unix-domain socket)
8
+ // macOS : LOCAL_PEERCRED (getsockopt) or xpc_connection_get_audit_token
9
+ // Windows : GetNamedPipeClientProcessId + OpenProcessToken over a named pipe
10
+ //
11
+ // Two-layer clarification from the ticket (D-193/D-194):
12
+ // F0.4 proves "real local OS process" (pid/uid/gid from the kernel).
13
+ // Swarm-lease binding (TRD §2.8) is a separate layer that maps a kernel-verified
14
+ // process to a named agent role — that is `resolveLeaseIdentity()` below.
15
+ //
16
+ // Lease-identity fix (GLM, round-3): the evaluator NEVER trusts `agentId` from
17
+ // the request payload. It looks up the lease record server-side by `leaseId`;
18
+ // the record's own `agentId` is authoritative. The payload copy is checked
19
+ // against the record (mismatch → reject) but never accepted as truth on its own.
20
+ //
21
+ // F0.4↔F0.5 interaction: each platform sandbox profile (F0.5) must ship a
22
+ // dedicated conformance fixture that exercises getPeerIdentity() from inside that
23
+ // specific sandbox before the profile is considered complete. If a sandbox
24
+ // profile blocks the IPC primitive a narrow, logged carve-out is emitted instead
25
+ // of a general sandbox loosening (see SANDBOX_CARVEOUT_NOTE below).
26
+ //
27
+ // Pure Node stdlib, ESM — no third-party dependencies.
28
+ // Requires a C compiler (cc/gcc/clang) at first call on Linux/macOS for the
29
+ // helper binary; the path is cached process-wide after first compile.
30
+
31
+ import { execFileSync, spawnSync } from 'node:child_process';
32
+ import { writeFileSync, mkdtempSync, mkdirSync, existsSync, readdirSync } from 'node:fs';
33
+ import { join, dirname } from 'node:path';
34
+ import { tmpdir, platform } from 'node:os';
35
+ import { fileURLToPath } from 'node:url';
36
+
37
+ const PLATFORM = platform();
38
+
39
+ // ---------------------------------------------------------------------------
40
+ // SANDBOX_CARVEOUT_NOTE
41
+ //
42
+ // When F0.4's IPC primitive cannot be exercised inside an F0.5 sandbox profile,
43
+ // the profile MUST emit a structured carve-out log entry of the form:
44
+ //
45
+ // { level: 'warn', event: 'f04_sandbox_carveout',
46
+ // primitive: <'SO_PEERCRED'|'LOCAL_PEERCRED'|'GetNamedPipeClientProcessId'>,
47
+ // sandbox: <profile-name>, reason: <string> }
48
+ //
49
+ // The carve-out is ONLY for the specific IPC primitive, never a general loosening.
50
+ // `knosky doctor` reads this log and surfaces it to the operator.
51
+ // ---------------------------------------------------------------------------
52
+ export const SANDBOX_CARVEOUT_EVENT = 'f04_sandbox_carveout';
53
+
54
+ // ---------------------------------------------------------------------------
55
+ // SO_PEERCRED helper binary (Linux)
56
+ // ---------------------------------------------------------------------------
57
+ //
58
+ // Node.js does not expose getsockopt(SO_PEERCRED) natively. We compile a tiny
59
+ // C helper on first use. The helper receives the socket fd on its own stdin (fd 0)
60
+ // and prints "pid uid gid\n" to stdout. Receiving via fd 0 lets Node pass the
61
+ // live fd without a subprocess fd-inheritance gap (the handle is passed directly
62
+ // in the stdio option array).
63
+ //
64
+ // The binary is placed under a process-scoped temp directory and reused for the
65
+ // process lifetime. On platforms where cc/gcc is unavailable the IPC identity
66
+ // layer degrades gracefully (returns null with a diagnostic).
67
+
68
+ const HELPER_SRC_LINUX = `
69
+ #define _GNU_SOURCE
70
+ #include <stdio.h>
71
+ #include <string.h>
72
+ #include <sys/socket.h>
73
+ #include <unistd.h>
74
+
75
+ /* Receive the socket fd on stdin (fd 0) via the Node.js stdio handle pass.
76
+ Call getsockopt SO_PEERCRED on that fd, print pid uid gid. */
77
+ int main(void) {
78
+ struct ucred cred;
79
+ memset(&cred, 0, sizeof(cred));
80
+ socklen_t len = sizeof(cred);
81
+ if (getsockopt(0, SOL_SOCKET, SO_PEERCRED, &cred, &len) < 0) {
82
+ perror("getsockopt SO_PEERCRED");
83
+ return 1;
84
+ }
85
+ printf("%d %d %d\\n", (int)cred.pid, (int)cred.uid, (int)cred.gid);
86
+ return 0;
87
+ }
88
+ `;
89
+
90
+ // macOS uses LOCAL_PEERCRED with struct xucred (different from Linux ucred).
91
+ const HELPER_SRC_MACOS = `
92
+ #include <stdio.h>
93
+ #include <string.h>
94
+ #include <sys/socket.h>
95
+ #include <sys/ucred.h>
96
+ #include <unistd.h>
97
+
98
+ int main(void) {
99
+ struct xucred cred;
100
+ memset(&cred, 0, sizeof(cred));
101
+ socklen_t len = sizeof(cred);
102
+ /* LOCAL_PEERCRED on macOS returns the peer's effective uid/gids.
103
+ Note: macOS does not include pid in xucred; pid is obtained via
104
+ LOCAL_PEEREPID (separate getsockopt call). */
105
+ if (getsockopt(0, SOL_LOCAL, LOCAL_PEERCRED, &cred, &len) < 0) {
106
+ perror("getsockopt LOCAL_PEERCRED");
107
+ return 1;
108
+ }
109
+ int pid = 0;
110
+ socklen_t pidlen = sizeof(pid);
111
+ /* LOCAL_PEEREPID = 7 on macOS */
112
+ getsockopt(0, SOL_LOCAL, 7, &pid, &pidlen);
113
+ printf("%d %d 0\\n", pid, (int)cred.cr_uid);
114
+ return 0;
115
+ }
116
+ `;
117
+
118
+ /** @type {string|null} cached path to the compiled helper binary */
119
+ let _helperPath = null;
120
+
121
+ /**
122
+ * Compile and cache the peercred helper binary for the current platform.
123
+ * Returns the absolute path to the compiled binary, or null if compilation fails.
124
+ *
125
+ * @returns {string|null}
126
+ */
127
+ function compilePeercredHelper() {
128
+ if (_helperPath !== null) return _helperPath;
129
+
130
+ const src = PLATFORM === 'linux' ? HELPER_SRC_LINUX
131
+ : PLATFORM === 'darwin' ? HELPER_SRC_MACOS
132
+ : null;
133
+ if (!src) return null; // Windows uses a different mechanism
134
+
135
+ let dir;
136
+ try {
137
+ dir = mkdtempSync(join(tmpdir(), 'knosky-ipc-'));
138
+ } catch {
139
+ return null;
140
+ }
141
+
142
+ const srcPath = join(dir, 'peercred.c');
143
+ const binPath = join(dir, 'peercred');
144
+
145
+ try {
146
+ writeFileSync(srcPath, src, 'utf8');
147
+ } catch {
148
+ return null;
149
+ }
150
+
151
+ // Try cc, then gcc, then clang in order.
152
+ const compilers = ['cc', 'gcc', 'clang'];
153
+ for (const cc of compilers) {
154
+ try {
155
+ execFileSync(cc, ['-O2', '-o', binPath, srcPath], { stdio: 'ignore', timeout: 15000 });
156
+ _helperPath = binPath;
157
+ return binPath;
158
+ } catch {
159
+ // try next compiler
160
+ }
161
+ }
162
+
163
+ return null; // no compiler available
164
+ }
165
+
166
+ // ---------------------------------------------------------------------------
167
+ // getPeerIdentity — main public API
168
+ // ---------------------------------------------------------------------------
169
+
170
+ /**
171
+ * @typedef {Object} PeerIdentity
172
+ * @property {number} pid - Kernel-reported PID of the peer process.
173
+ * @property {number} uid - Kernel-reported UID of the peer process.
174
+ * @property {number} gid - Kernel-reported GID of the peer process (0 on macOS).
175
+ * @property {string} mechanism - Which kernel primitive was used.
176
+ * @property {string} platform - OS platform string.
177
+ */
178
+
179
+ /**
180
+ * Obtain the kernel-verified identity of the process connected to `socketHandle`.
181
+ *
182
+ * `socketHandle` must be a live `net.Socket._handle` (a TCP/pipe handle object
183
+ * from Node.js's internal binding layer) that exposes an `fd` property. The
184
+ * function reads the socket's peer credentials directly from the OS kernel —
185
+ * the peer process cannot forge these values.
186
+ *
187
+ * Returns `null` (never throws) when:
188
+ * - the platform is not yet supported
189
+ * - the handle has no accessible fd
190
+ * - the compiler is unavailable (Linux/macOS)
191
+ * - the kernel call itself fails (e.g. inside an F0.5 sandbox)
192
+ *
193
+ * When returning `null` on Linux/macOS the caller SHOULD emit a
194
+ * SANDBOX_CARVEOUT_EVENT log entry identifying the specific primitive that
195
+ * failed; that entry surfaces in `knosky doctor`.
196
+ *
197
+ * @param {object} socketHandle - net.Socket._handle from an accepted connection.
198
+ * @returns {PeerIdentity|null}
199
+ */
200
+ export function getPeerIdentity(socketHandle) {
201
+ if (PLATFORM === 'linux' || PLATFORM === 'darwin') {
202
+ return _getPeerIdentityUnix(socketHandle);
203
+ }
204
+ if (PLATFORM === 'win32') {
205
+ return _getPeerIdentityWin32(socketHandle);
206
+ }
207
+ return null;
208
+ }
209
+
210
+ /**
211
+ * Linux / macOS implementation: SO_PEERCRED / LOCAL_PEERCRED via helper binary.
212
+ *
213
+ * @param {object} socketHandle
214
+ * @returns {PeerIdentity|null}
215
+ */
216
+ function _getPeerIdentityUnix(socketHandle) {
217
+ const helperBin = compilePeercredHelper();
218
+ if (!helperBin) return null;
219
+
220
+ if (!socketHandle || typeof socketHandle !== 'object') return null;
221
+
222
+ // Pass the socket fd to the helper via its stdin slot (position 0 in stdio).
223
+ // Node's spawnSync supports passing a handle object directly in the stdio
224
+ // array; this causes libuv to dup the fd into the child's stdin.
225
+ let result;
226
+ try {
227
+ result = spawnSync(helperBin, [], {
228
+ stdio: [socketHandle, 'pipe', 'ignore'],
229
+ encoding: 'utf8',
230
+ timeout: 5000,
231
+ });
232
+ } catch {
233
+ return null;
234
+ }
235
+
236
+ if (result.status !== 0 || result.signal !== null) return null;
237
+
238
+ const parts = (result.stdout || '').trim().split(' ').map(Number);
239
+ if (parts.length < 3 || parts.some(n => !Number.isInteger(n) || n < 0)) return null;
240
+
241
+ const [pid, uid, gid] = parts;
242
+ // pid === 0 is suspicious on Linux (only kernel threads have pid 0).
243
+ // Accept on macOS where LOCAL_PEEREPID may return 0 in some edge cases.
244
+ if (PLATFORM === 'linux' && pid === 0) return null;
245
+
246
+ return {
247
+ pid,
248
+ uid,
249
+ gid,
250
+ mechanism: PLATFORM === 'linux' ? 'SO_PEERCRED' : 'LOCAL_PEERCRED',
251
+ platform: PLATFORM,
252
+ };
253
+ }
254
+
255
+ /**
256
+ * Windows implementation: GetNamedPipeClientProcessId + OpenProcessToken.
257
+ *
258
+ * On Windows, knosky uses a named pipe for local IPC. The kernel provides
259
+ * the client's PID via GetNamedPipeClientProcessId; the token (uid analogue)
260
+ * is obtained via ImpersonateNamedPipeClient + OpenProcessToken.
261
+ *
262
+ * This implementation calls a PowerShell one-liner with the pipe handle so
263
+ * that no C++ addon is required for the Windows path.
264
+ *
265
+ * @param {object} socketHandle
266
+ * @returns {PeerIdentity|null}
267
+ */
268
+ function _getPeerIdentityWin32(socketHandle) {
269
+ // Windows named pipes expose the fd as a HANDLE value.
270
+ const fd = socketHandle?.fd;
271
+ if (typeof fd !== 'number' || fd < 0) return null;
272
+
273
+ // PowerShell script: call GetNamedPipeClientProcessId on the handle value,
274
+ // output JSON {pid, sid}. The SID encodes the uid analogue on Windows.
275
+ const ps = [
276
+ '$h=[IntPtr]' + fd + ';',
277
+ '$pid=0;',
278
+ '[void][KnoskySec.Pipe]::GetNamedPipeClientProcessId($h,[ref]$pid);',
279
+ '$tok=[System.IntPtr]::Zero;',
280
+ '[KnoskySec.Pipe]::OpenProcessToken([Diagnostics.Process]::GetProcessById($pid).Handle,8,[ref]$tok)|Out-Null;',
281
+ '$id=New-Object System.Security.Principal.WindowsIdentity $tok;',
282
+ 'Write-Output ("{0} {1}" -f $pid,$id.User.Value)',
283
+ ].join(' ');
284
+
285
+ let result;
286
+ try {
287
+ result = spawnSync('powershell.exe', ['-NoProfile', '-Command', ps], {
288
+ encoding: 'utf8',
289
+ timeout: 10000,
290
+ });
291
+ } catch {
292
+ return null;
293
+ }
294
+
295
+ if (result.status !== 0) return null;
296
+
297
+ const line = (result.stdout || '').trim();
298
+ const m = line.match(/^(\d+)\s+(.+)$/);
299
+ if (!m) return null;
300
+
301
+ const pid = Number(m[1]);
302
+ if (!Number.isInteger(pid) || pid <= 0) return null;
303
+
304
+ return {
305
+ pid,
306
+ uid: -1, // Windows uses SID, not numeric uid
307
+ gid: -1,
308
+ sid: m[2], // Windows Security Identifier
309
+ mechanism: 'GetNamedPipeClientProcessId',
310
+ platform: 'win32',
311
+ };
312
+ }
313
+
314
+ // ---------------------------------------------------------------------------
315
+ // resolveLeaseIdentity — swarm-lease anti-spoofing (GLM round-3 fix)
316
+ // ---------------------------------------------------------------------------
317
+
318
+ /**
319
+ * @typedef {Object} LeaseRecord
320
+ * @property {string} leaseId - Stable, server-assigned lease identifier.
321
+ * @property {string} agentId - Authoritative agent identity (server-side record).
322
+ * @property {number} [pid] - PID of the process that registered the lease (optional).
323
+ * @property {number} [uid] - UID of the process that registered the lease (optional).
324
+ * @property {string} [status] - 'active' | 'expired' | 'revoked'.
325
+ */
326
+
327
+ /**
328
+ * Resolve the authoritative agent identity for an incoming request.
329
+ *
330
+ * CRITICAL invariant (GLM round-3): the evaluator NEVER trusts `payloadAgentId`
331
+ * (the `agentId` field from the request payload) as truth. Instead it looks up
332
+ * the lease record server-side using `leaseId`. The record's own `agentId` is
333
+ * returned as the authoritative identity.
334
+ *
335
+ * If `payloadAgentId` is supplied AND does not match the record's `agentId`,
336
+ * the call returns a spoofing-attempt error rather than accepting either value.
337
+ * Payload copy is checked-not-trusted: the check closes the agent-spoofing-
338
+ * under-shared-UID gap described in the ticket.
339
+ *
340
+ * @param {Map<string, LeaseRecord>} leaseStore
341
+ * Server-side, authoritative store of live lease records (keyed by leaseId).
342
+ * @param {string} leaseId
343
+ * The leaseId extracted from the incoming request (used only as a lookup key).
344
+ * @param {string|null} [payloadAgentId]
345
+ * The agentId the request self-asserts (optional; never used as truth).
346
+ * @returns {{ ok: true, agentId: string, lease: LeaseRecord }
347
+ * |{ ok: false, reason: string }}
348
+ */
349
+ export function resolveLeaseIdentity(leaseStore, leaseId, payloadAgentId = null) {
350
+ if (!leaseId || typeof leaseId !== 'string') {
351
+ return { ok: false, reason: 'missing_lease_id' };
352
+ }
353
+
354
+ const lease = leaseStore instanceof Map ? leaseStore.get(leaseId) : null;
355
+ if (!lease) {
356
+ return { ok: false, reason: 'unknown_lease_id' };
357
+ }
358
+
359
+ // TTL: reject active leases past expires_at even if status not swept yet
360
+ if (lease.expires_at) {
361
+ const exp = Date.parse(lease.expires_at);
362
+ if (Number.isFinite(exp) && exp <= Date.now()) {
363
+ return { ok: false, reason: 'lease_expired' };
364
+ }
365
+ }
366
+
367
+ if (lease.status === 'expired') {
368
+ return { ok: false, reason: 'lease_expired' };
369
+ }
370
+ if (lease.status === 'revoked') {
371
+ return { ok: false, reason: 'lease_revoked' };
372
+ }
373
+
374
+ const authoritative = lease.agentId;
375
+ if (!authoritative || typeof authoritative !== 'string') {
376
+ return { ok: false, reason: 'lease_missing_agent_id' };
377
+ }
378
+
379
+ // Check-not-trust: if the request also asserts an agentId, verify it agrees
380
+ // with the server-side record. A mismatch signals a spoofing attempt.
381
+ if (payloadAgentId !== null && payloadAgentId !== undefined) {
382
+ if (payloadAgentId !== authoritative) {
383
+ return {
384
+ ok: false,
385
+ reason: 'payload_agent_id_mismatch',
386
+ // Do not echo back either value — let the caller log as needed.
387
+ };
388
+ }
389
+ }
390
+
391
+ return { ok: true, agentId: authoritative, lease };
392
+ }
393
+
394
+ // ---------------------------------------------------------------------------
395
+ // conformanceReport — used by F0.4↔F0.5 sandbox conformance fixtures
396
+ // ---------------------------------------------------------------------------
397
+
398
+ /**
399
+ * Run a self-check of the F0.4 identity mechanism and return a structured report.
400
+ *
401
+ * Called by each F0.5 sandbox conformance fixture to verify that F0.4 still
402
+ * functions correctly inside that specific sandbox before the profile is
403
+ * considered complete. If the check fails the fixture MUST emit a
404
+ * SANDBOX_CARVEOUT_EVENT log entry for the specific IPC primitive and surface
405
+ * it in `knosky doctor`.
406
+ *
407
+ * The report shape:
408
+ * ```
409
+ * {
410
+ * platform: string,
411
+ * mechanism: 'SO_PEERCRED'|'LOCAL_PEERCRED'|'GetNamedPipeClientProcessId'|'unsupported',
412
+ * compilerAvailable: boolean, // Linux / macOS
413
+ * selfTestPassed: boolean,
414
+ * selfTestPid: number|null, // must equal process.pid when selfTestPassed
415
+ * error: string|null,
416
+ * }
417
+ * ```
418
+ *
419
+ * @returns {Promise<object>}
420
+ */
421
+ export async function conformanceReport() {
422
+ if (PLATFORM === 'win32') {
423
+ // Windows: simple named-pipe self-test is complex to set up here;
424
+ // return a partial report. Full conformance is done by the F0.5 fixture.
425
+ return {
426
+ platform: 'win32',
427
+ mechanism: 'GetNamedPipeClientProcessId',
428
+ compilerAvailable: null,
429
+ selfTestPassed: false,
430
+ selfTestPid: null,
431
+ error: 'windows conformance test requires F0.5 fixture setup',
432
+ };
433
+ }
434
+
435
+ const helperBin = compilePeercredHelper();
436
+ const mechanism = PLATFORM === 'linux' ? 'SO_PEERCRED' : 'LOCAL_PEERCRED';
437
+
438
+ if (!helperBin) {
439
+ return {
440
+ platform: PLATFORM,
441
+ mechanism,
442
+ compilerAvailable: false,
443
+ selfTestPassed: false,
444
+ selfTestPid: null,
445
+ error: 'no C compiler available (cc/gcc/clang); could not compile peercred helper',
446
+ };
447
+ }
448
+
449
+ // Run a loopback self-test: this process connects to itself over a Unix socket
450
+ // and reads back its own pid via getPeerIdentity().
451
+ const { createServer, connect } = await import('node:net');
452
+ const { join: pjoin } = await import('node:path');
453
+ const { mkdtempSync: mkdtmp, unlinkSync: unlink } = await import('node:fs');
454
+
455
+ const sockDir = mkdtmp(join(tmpdir(), 'knosky-f04-conf-'));
456
+ const sockPath = pjoin(sockDir, 'self.sock');
457
+
458
+ let selfTestPassed = false;
459
+ let selfTestPid = null;
460
+ let error = null;
461
+
462
+ try {
463
+ await new Promise((resolve, reject) => {
464
+ const srv = createServer((sock) => {
465
+ const identity = getPeerIdentity(sock._handle);
466
+ if (identity && identity.pid === process.pid) {
467
+ selfTestPassed = true;
468
+ selfTestPid = identity.pid;
469
+ } else {
470
+ error = identity
471
+ ? `pid mismatch: expected ${process.pid}, got ${identity.pid}`
472
+ : 'getPeerIdentity returned null';
473
+ }
474
+ sock.destroy();
475
+ srv.close(resolve);
476
+ });
477
+ srv.on('error', reject);
478
+ srv.listen(sockPath, () => {
479
+ const c = connect(sockPath);
480
+ c.on('error', reject);
481
+ c.once('connect', () => c.destroy());
482
+ });
483
+ });
484
+ } catch (e) {
485
+ error = String(e?.message || e);
486
+ selfTestPassed = false;
487
+ }
488
+
489
+ try { unlink(sockPath); } catch { /* best effort */ }
490
+ try { unlink(sockDir); } catch { /* best effort */ }
491
+
492
+ return {
493
+ platform: PLATFORM,
494
+ mechanism,
495
+ compilerAvailable: true,
496
+ selfTestPassed,
497
+ selfTestPid,
498
+ error,
499
+ };
500
+ }