fullcourtdefense-cli 1.21.29 → 1.21.30
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/dist/commands/daemon.d.ts +2 -0
- package/dist/commands/daemon.js +12 -0
- package/dist/commands/onboard.js +96 -7
- package/dist/commands/onboardingJournal.d.ts +1 -1
- package/dist/commands/onboardingJournal.js +4 -0
- package/dist/envDiagnostics.d.ts +46 -0
- package/dist/envDiagnostics.js +98 -0
- package/dist/version.json +1 -1
- package/package.json +1 -1
|
@@ -46,5 +46,7 @@ export declare function spawnDetachedDaemon(): boolean;
|
|
|
46
46
|
export declare function pidIsAlive(pid: number): boolean;
|
|
47
47
|
export declare function macosLaunchdPath(currentPath?: string, execPath?: string): string;
|
|
48
48
|
/** Whether the daemon has been registered to start automatically. */
|
|
49
|
+
/** Is the 5-minute watchdog scheduled task registered? (Windows-only feature.) */
|
|
50
|
+
export declare function isWatchdogTaskInstalled(): boolean;
|
|
49
51
|
export declare function isDaemonAutostartInstalled(): boolean;
|
|
50
52
|
export declare function daemonCommand(args: DaemonArgs, config: BotGuardConfig): Promise<void>;
|
package/dist/commands/daemon.js
CHANGED
|
@@ -40,6 +40,7 @@ exports.daemonRuntimeState = daemonRuntimeState;
|
|
|
40
40
|
exports.spawnDetachedDaemon = spawnDetachedDaemon;
|
|
41
41
|
exports.pidIsAlive = pidIsAlive;
|
|
42
42
|
exports.macosLaunchdPath = macosLaunchdPath;
|
|
43
|
+
exports.isWatchdogTaskInstalled = isWatchdogTaskInstalled;
|
|
43
44
|
exports.isDaemonAutostartInstalled = isDaemonAutostartInstalled;
|
|
44
45
|
exports.daemonCommand = daemonCommand;
|
|
45
46
|
const fs = __importStar(require("fs"));
|
|
@@ -1290,6 +1291,17 @@ function systemdUnitPath() {
|
|
|
1290
1291
|
return path.join(base, 'systemd', 'user', SYSTEMD_UNIT);
|
|
1291
1292
|
}
|
|
1292
1293
|
/** Whether the daemon has been registered to start automatically. */
|
|
1294
|
+
/** Is the 5-minute watchdog scheduled task registered? (Windows-only feature.) */
|
|
1295
|
+
function isWatchdogTaskInstalled() {
|
|
1296
|
+
if (process.platform !== 'win32')
|
|
1297
|
+
return false;
|
|
1298
|
+
const result = (0, child_process_1.spawnSync)('schtasks', ['/Query', '/TN', WATCHDOG_TASK_NAME], {
|
|
1299
|
+
stdio: 'ignore',
|
|
1300
|
+
timeout: 5_000,
|
|
1301
|
+
windowsHide: true,
|
|
1302
|
+
});
|
|
1303
|
+
return result.status === 0;
|
|
1304
|
+
}
|
|
1293
1305
|
function isDaemonAutostartInstalled() {
|
|
1294
1306
|
if (process.platform === 'win32') {
|
|
1295
1307
|
const result = (0, child_process_1.spawnSync)('schtasks', ['/Query', '/TN', TASK_NAME], {
|
package/dist/commands/onboard.js
CHANGED
|
@@ -53,6 +53,8 @@ const machineIdentity_1 = require("../machineIdentity");
|
|
|
53
53
|
const appDetection_1 = require("../appDetection");
|
|
54
54
|
const integrity_1 = require("../integrity");
|
|
55
55
|
const telemetry_1 = require("../telemetry");
|
|
56
|
+
const envDiagnostics_1 = require("../envDiagnostics");
|
|
57
|
+
const daemonForensics_1 = require("../daemonForensics");
|
|
56
58
|
const onboardingJournal_1 = require("./onboardingJournal");
|
|
57
59
|
const GREEN = '\x1b[32m';
|
|
58
60
|
const RED = '\x1b[31m';
|
|
@@ -246,7 +248,7 @@ async function onboardCommand(args, config) {
|
|
|
246
248
|
const apiUrl = creds.apiUrl;
|
|
247
249
|
const token = (args.token || process.env.FCD_ENROLL_TOKEN || '').trim();
|
|
248
250
|
// 1. Connectivity.
|
|
249
|
-
console.log(`${BOLD}[1/
|
|
251
|
+
console.log(`${BOLD}[1/7] Checking compatibility and connectivity…${RESET}`);
|
|
250
252
|
mark('preflight', 'running');
|
|
251
253
|
// A stale cmd-guard AutoRun (registry points at a deleted autorun script)
|
|
252
254
|
// poisons every cmd.exe exit code on the machine — child spawns and the MSI's
|
|
@@ -280,7 +282,7 @@ async function onboardCommand(args, config) {
|
|
|
280
282
|
}
|
|
281
283
|
mark('preflight', 'completed');
|
|
282
284
|
// 2. Enrollment.
|
|
283
|
-
console.log(`\n${BOLD}[2/
|
|
285
|
+
console.log(`\n${BOLD}[2/7] Enrolling this machine…${RESET}`);
|
|
284
286
|
mark('enrollment', 'running');
|
|
285
287
|
const alreadyEnrolled = Boolean(creds.shieldId && creds.shieldKey);
|
|
286
288
|
if (dryRun) {
|
|
@@ -305,7 +307,7 @@ async function onboardCommand(args, config) {
|
|
|
305
307
|
// Re-read config: login just wrote fresh shield credentials to ~/.fullcourtdefense.yml.
|
|
306
308
|
const freshConfig = (0, config_1.loadConfig)();
|
|
307
309
|
// 3. Protection.
|
|
308
|
-
console.log(`\n${BOLD}[3/
|
|
310
|
+
console.log(`\n${BOLD}[3/7] Installing protection (install-all)…${RESET}`);
|
|
309
311
|
if (dryRun) {
|
|
310
312
|
mark('protection', 'skipped', 'would install MCP gateways, IDE hooks, and terminal guards');
|
|
311
313
|
}
|
|
@@ -334,7 +336,7 @@ async function onboardCommand(args, config) {
|
|
|
334
336
|
return;
|
|
335
337
|
}
|
|
336
338
|
}
|
|
337
|
-
console.log(`\n${BOLD}[4/
|
|
339
|
+
console.log(`\n${BOLD}[4/7] Installing background protection…${RESET}`);
|
|
338
340
|
if (dryRun || args.noDaemon === 'true') {
|
|
339
341
|
mark('daemon', 'skipped', dryRun ? 'would register the resident daemon' : 'disabled by --no-daemon true');
|
|
340
342
|
}
|
|
@@ -355,7 +357,7 @@ async function onboardCommand(args, config) {
|
|
|
355
357
|
}
|
|
356
358
|
// Discovery and scheduling are useful fleet telemetry, but must never mark a
|
|
357
359
|
// device as unprotected or prevent a repair of the enforcement surfaces.
|
|
358
|
-
console.log(`\n${BOLD}[5/
|
|
360
|
+
console.log(`\n${BOLD}[5/7] Recording optional discovery and schedule…${RESET}`);
|
|
359
361
|
if (dryRun || args.discover === 'false') {
|
|
360
362
|
mark('discovery', 'skipped', dryRun ? 'would upload desktop discovery' : 'disabled by --discover false');
|
|
361
363
|
}
|
|
@@ -383,8 +385,38 @@ async function onboardCommand(args, config) {
|
|
|
383
385
|
mark('schedule', 'failed', undefined, error instanceof Error ? error.message : String(error));
|
|
384
386
|
}
|
|
385
387
|
}
|
|
386
|
-
// 4.
|
|
387
|
-
|
|
388
|
+
// 4. Environment diagnostics — the "will this machine stay alive?" snapshot.
|
|
389
|
+
// Runs while we still have an interactive session on the (possibly EDR-
|
|
390
|
+
// hardened) machine: EDR/AV inventory, PowerShell/DPAPI health, daemon
|
|
391
|
+
// kill-detection (alive now AND still alive seconds later), watchdog task,
|
|
392
|
+
// and any crash post-mortem from a previous install. Never critical — the
|
|
393
|
+
// findings ship to the fleet console in the first heartbeat below.
|
|
394
|
+
console.log(`\n${BOLD}[6/7] Collecting environment diagnostics…${RESET}`);
|
|
395
|
+
let envDiag;
|
|
396
|
+
if (dryRun) {
|
|
397
|
+
mark('diagnostics', 'skipped', 'would probe EDR/AV, PowerShell health, and daemon liveness');
|
|
398
|
+
}
|
|
399
|
+
else {
|
|
400
|
+
mark('diagnostics', 'running');
|
|
401
|
+
try {
|
|
402
|
+
envDiag = await (0, envDiagnostics_1.collectEnvironmentDiagnostics)({ recheckDelayMs: args.noDaemon === 'true' ? 0 : 5_000 });
|
|
403
|
+
const summary = (0, envDiagnostics_1.summarizeEnvironmentDiagnostics)(envDiag);
|
|
404
|
+
console.log(` ${DIM}${summary}${RESET}`);
|
|
405
|
+
if ((0, envDiagnostics_1.environmentLooksHealthy)(envDiag)) {
|
|
406
|
+
mark('diagnostics', 'completed', summary);
|
|
407
|
+
}
|
|
408
|
+
else {
|
|
409
|
+
// Non-critical: a hostile environment is REPORTED, not treated as an
|
|
410
|
+
// install failure — the protection surfaces below are what gate.
|
|
411
|
+
mark('diagnostics', 'failed', summary, summary);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
catch (error) {
|
|
415
|
+
mark('diagnostics', 'failed', undefined, error instanceof Error ? error.message : String(error));
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
// 5. Verification.
|
|
419
|
+
console.log(`\n${BOLD}[7/7] Verifying protection surfaces…${RESET}`);
|
|
388
420
|
mark('verification', 'running');
|
|
389
421
|
const postCreds = (0, config_1.resolveCliCredentials)((0, config_1.loadConfig)(), { apiUrl });
|
|
390
422
|
// Immediate heartbeat: the machine must appear ONLINE in the fleet console
|
|
@@ -409,9 +441,31 @@ async function onboardCommand(args, config) {
|
|
|
409
441
|
integrityOk: integrity.ok,
|
|
410
442
|
integrityReasons: integrity.reasons,
|
|
411
443
|
integrityCheckedAt: integrity.checkedAt,
|
|
444
|
+
// Environment diagnostics ride the FIRST heartbeat: the fleet console
|
|
445
|
+
// knows this machine's EDR/AV products, PowerShell health, and any
|
|
446
|
+
// previous crash the moment onboarding finishes — before the machine
|
|
447
|
+
// has any chance to go silent.
|
|
448
|
+
securityAgents: envDiag?.securityAgents,
|
|
449
|
+
powershellSpawnOk: envDiag?.powershellSpawnOk,
|
|
450
|
+
powershellDecryptOk: envDiag?.powershellDecryptOk,
|
|
451
|
+
lastCrash: envDiag?.lastCrash
|
|
452
|
+
? {
|
|
453
|
+
version: envDiag.lastCrash.version,
|
|
454
|
+
startedAt: envDiag.lastCrash.startedAt,
|
|
455
|
+
lastAliveAt: envDiag.lastCrash.lastAliveAt,
|
|
456
|
+
detectedAt: envDiag.lastCrash.detectedAt,
|
|
457
|
+
detectedBy: envDiag.lastCrash.detectedBy,
|
|
458
|
+
}
|
|
459
|
+
: undefined,
|
|
412
460
|
timeoutMs: 8_000,
|
|
413
461
|
});
|
|
414
462
|
firstHeartbeatOk = flush !== null;
|
|
463
|
+
if (firstHeartbeatOk && envDiag?.lastCrash) {
|
|
464
|
+
try {
|
|
465
|
+
(0, daemonForensics_1.markPostmortemReported)();
|
|
466
|
+
}
|
|
467
|
+
catch { /* best-effort */ }
|
|
468
|
+
}
|
|
415
469
|
}
|
|
416
470
|
catch { /* the daemon heartbeat remains the backstop */ }
|
|
417
471
|
}
|
|
@@ -455,6 +509,41 @@ async function onboardCommand(args, config) {
|
|
|
455
509
|
? 'this machine now shows ONLINE in the fleet console'
|
|
456
510
|
: dryRun ? 'skipped (dry run)' : 'not delivered yet — the daemon retries within 5 minutes',
|
|
457
511
|
},
|
|
512
|
+
// --- environment diagnostics (informational: report, never block) -------
|
|
513
|
+
{
|
|
514
|
+
label: 'Daemon survived post-install liveness check',
|
|
515
|
+
ok: envDiag ? envDiag.daemonSurvivedRecheck !== false && envDiag.daemonAlive : true,
|
|
516
|
+
optional: true,
|
|
517
|
+
detail: !envDiag
|
|
518
|
+
? 'skipped (dry run)'
|
|
519
|
+
: envDiag.daemonSurvivedRecheck === false
|
|
520
|
+
? 'daemon was KILLED seconds after starting — security software on this machine is terminating it'
|
|
521
|
+
: envDiag.daemonAlive ? `pid ${envDiag.daemonPid}` : 'daemon not running — the watchdog will start it within 5 minutes',
|
|
522
|
+
},
|
|
523
|
+
{
|
|
524
|
+
label: 'Watchdog liveness beacon (5-minute health reports)',
|
|
525
|
+
ok: envDiag ? envDiag.watchdogTaskInstalled : true,
|
|
526
|
+
optional: true,
|
|
527
|
+
detail: envDiag && !envDiag.watchdogTaskInstalled && process.platform === 'win32'
|
|
528
|
+
? 'task not registered — the daemon re-creates it at every start'
|
|
529
|
+
: undefined,
|
|
530
|
+
},
|
|
531
|
+
{
|
|
532
|
+
label: 'PowerShell available for secure key storage',
|
|
533
|
+
ok: envDiag ? envDiag.powershellSpawnOk !== false : true,
|
|
534
|
+
optional: true,
|
|
535
|
+
detail: envDiag?.powershellSpawnOk === false
|
|
536
|
+
? 'PowerShell is blocked on this machine (security policy) — credentials use a PowerShell-free path'
|
|
537
|
+
: envDiag?.powershellDecryptOk === false ? 'PowerShell runs but the stored key did not decrypt' : undefined,
|
|
538
|
+
},
|
|
539
|
+
{
|
|
540
|
+
label: 'Security software inventory',
|
|
541
|
+
ok: true,
|
|
542
|
+
optional: true,
|
|
543
|
+
detail: envDiag?.securityAgents
|
|
544
|
+
? envDiag.securityAgents.length ? envDiag.securityAgents.join(', ') : 'none detected'
|
|
545
|
+
: 'not probed (non-Windows or dry run)',
|
|
546
|
+
},
|
|
458
547
|
];
|
|
459
548
|
console.log('');
|
|
460
549
|
for (const check of checks)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type OnboardingStepName = 'preflight' | 'enrollment' | 'protection' | 'daemon' | 'discovery' | 'schedule' | 'verification';
|
|
1
|
+
export type OnboardingStepName = 'preflight' | 'enrollment' | 'protection' | 'daemon' | 'discovery' | 'schedule' | 'diagnostics' | 'verification';
|
|
2
2
|
export type OnboardingStepStatus = 'pending' | 'running' | 'completed' | 'failed' | 'skipped';
|
|
3
3
|
export interface OnboardingStep {
|
|
4
4
|
status: OnboardingStepStatus;
|
|
@@ -48,6 +48,10 @@ const STEP_DEFINITIONS = [
|
|
|
48
48
|
['daemon', true],
|
|
49
49
|
['discovery', false],
|
|
50
50
|
['schedule', false],
|
|
51
|
+
// Post-install environment diagnostics (EDR/AV inventory, PowerShell
|
|
52
|
+
// health, watchdog + daemon liveness). Never critical: diagnostics report
|
|
53
|
+
// problems, they must not BE one.
|
|
54
|
+
['diagnostics', false],
|
|
51
55
|
['verification', true],
|
|
52
56
|
];
|
|
53
57
|
function onboardingJournalPath(home = os.homedir()) {
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { DaemonPostmortem } from './daemonForensics';
|
|
2
|
+
/**
|
|
3
|
+
* Post-install environment diagnostics — the "will this machine stay alive?"
|
|
4
|
+
* snapshot taken at the END of onboarding, while we still have an interactive
|
|
5
|
+
* session. Fleet machines are usually remote and locked-down (bank laptops,
|
|
6
|
+
* EDR); this is our best — sometimes only — chance to capture:
|
|
7
|
+
*
|
|
8
|
+
* - which EDR/AV products run here (read-only service probe, no PowerShell)
|
|
9
|
+
* - whether PowerShell can spawn + whether the DPAPI key decrypts
|
|
10
|
+
* - whether the daemon that onboarding just started is alive AND SURVIVES
|
|
11
|
+
* a re-check a few seconds later (an EDR that kills the daemon does it
|
|
12
|
+
* within seconds — catching that at install time beats discovering a
|
|
13
|
+
* silent machine a day later)
|
|
14
|
+
* - whether the watchdog liveness-beacon task actually registered
|
|
15
|
+
* - any crash post-mortem left by a previous install's daemon
|
|
16
|
+
*
|
|
17
|
+
* Contract: NEVER throws, never blocks onboarding for more than the re-check
|
|
18
|
+
* delay, every field is optional/best-effort. Diagnostics report problems —
|
|
19
|
+
* they must never BE one.
|
|
20
|
+
*/
|
|
21
|
+
export interface EnvironmentDiagnostics {
|
|
22
|
+
/** EDR/AV product names detected (Windows service probe). */
|
|
23
|
+
securityAgents?: string[];
|
|
24
|
+
powershellSpawnOk?: boolean;
|
|
25
|
+
powershellDecryptOk?: boolean;
|
|
26
|
+
daemonAlive: boolean;
|
|
27
|
+
daemonPid?: number;
|
|
28
|
+
/** Did the daemon still exist recheckDelayMs later? (false = killed within seconds — EDR signature) */
|
|
29
|
+
daemonSurvivedRecheck?: boolean;
|
|
30
|
+
watchdogTaskInstalled: boolean;
|
|
31
|
+
/** Unreported crash record from a previous daemon on this machine. */
|
|
32
|
+
lastCrash?: DaemonPostmortem;
|
|
33
|
+
collectedAt: string;
|
|
34
|
+
}
|
|
35
|
+
export declare function collectEnvironmentDiagnostics(options?: {
|
|
36
|
+
recheckDelayMs?: number;
|
|
37
|
+
}): Promise<EnvironmentDiagnostics>;
|
|
38
|
+
/** One-line human summary for the onboarding journal / MSI screen / daemon log. */
|
|
39
|
+
export declare function summarizeEnvironmentDiagnostics(diag: EnvironmentDiagnostics): string;
|
|
40
|
+
/**
|
|
41
|
+
* Verdict for the onboarding journal: healthy unless something on this
|
|
42
|
+
* machine is actively interfering (daemon killed, PowerShell blocked, or a
|
|
43
|
+
* fresh unexplained crash). Missing watchdog alone is a warning, not
|
|
44
|
+
* interference — the daemon self-heals it at every boot.
|
|
45
|
+
*/
|
|
46
|
+
export declare function environmentLooksHealthy(diag: EnvironmentDiagnostics): boolean;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.collectEnvironmentDiagnostics = collectEnvironmentDiagnostics;
|
|
4
|
+
exports.summarizeEnvironmentDiagnostics = summarizeEnvironmentDiagnostics;
|
|
5
|
+
exports.environmentLooksHealthy = environmentLooksHealthy;
|
|
6
|
+
const config_1 = require("./config");
|
|
7
|
+
const daemon_1 = require("./commands/daemon");
|
|
8
|
+
const securityAgents_1 = require("./securityAgents");
|
|
9
|
+
const daemonForensics_1 = require("./daemonForensics");
|
|
10
|
+
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
|
11
|
+
async function collectEnvironmentDiagnostics(options = {}) {
|
|
12
|
+
const recheckDelayMs = options.recheckDelayMs ?? 5_000;
|
|
13
|
+
const diag = {
|
|
14
|
+
daemonAlive: false,
|
|
15
|
+
watchdogTaskInstalled: false,
|
|
16
|
+
collectedAt: new Date().toISOString(),
|
|
17
|
+
};
|
|
18
|
+
try {
|
|
19
|
+
const agents = (0, securityAgents_1.getSecurityAgentsReport)();
|
|
20
|
+
if (agents)
|
|
21
|
+
diag.securityAgents = agents.products;
|
|
22
|
+
}
|
|
23
|
+
catch { /* best-effort */ }
|
|
24
|
+
// Exercise the real credential path once so PowerShell health reflects THIS
|
|
25
|
+
// machine's DPAPI reality (spawn blocked vs blob undecryptable). Onboarding
|
|
26
|
+
// is interactive and already ran PowerShell to store the key, so one more
|
|
27
|
+
// spawn here is safe — and it's the exact operation the daemon depends on.
|
|
28
|
+
try {
|
|
29
|
+
if (process.platform === 'win32') {
|
|
30
|
+
(0, config_1.resolveCliCredentials)((0, config_1.loadConfig)());
|
|
31
|
+
const health = (0, config_1.getPowershellHealth)();
|
|
32
|
+
if (health) {
|
|
33
|
+
diag.powershellSpawnOk = health.spawnOk;
|
|
34
|
+
diag.powershellDecryptOk = health.decryptOk;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
catch { /* health stays unknown */ }
|
|
39
|
+
try {
|
|
40
|
+
const state = (0, daemon_1.daemonRuntimeState)();
|
|
41
|
+
diag.daemonAlive = state.alive;
|
|
42
|
+
diag.daemonPid = state.pid;
|
|
43
|
+
if (state.alive && recheckDelayMs > 0) {
|
|
44
|
+
// The EDR-kill signature: process starts, then dies within seconds.
|
|
45
|
+
await sleep(recheckDelayMs);
|
|
46
|
+
diag.daemonSurvivedRecheck = (0, daemon_1.daemonRuntimeState)().alive;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
catch { /* daemon state unknown */ }
|
|
50
|
+
try {
|
|
51
|
+
diag.watchdogTaskInstalled = (0, daemon_1.isWatchdogTaskInstalled)();
|
|
52
|
+
}
|
|
53
|
+
catch { /* stays false */ }
|
|
54
|
+
try {
|
|
55
|
+
const crash = (0, daemonForensics_1.readPostmortem)();
|
|
56
|
+
if (crash && !crash.reportedAt)
|
|
57
|
+
diag.lastCrash = crash;
|
|
58
|
+
}
|
|
59
|
+
catch { /* best-effort */ }
|
|
60
|
+
return diag;
|
|
61
|
+
}
|
|
62
|
+
/** One-line human summary for the onboarding journal / MSI screen / daemon log. */
|
|
63
|
+
function summarizeEnvironmentDiagnostics(diag) {
|
|
64
|
+
const parts = [];
|
|
65
|
+
if (diag.securityAgents) {
|
|
66
|
+
parts.push(diag.securityAgents.length ? `EDR/AV: ${diag.securityAgents.join(', ')}` : 'EDR/AV: none detected');
|
|
67
|
+
}
|
|
68
|
+
if (diag.powershellSpawnOk !== undefined) {
|
|
69
|
+
parts.push(diag.powershellSpawnOk
|
|
70
|
+
? `PowerShell OK${diag.powershellDecryptOk === false ? ' (key decrypt FAILED)' : ''}`
|
|
71
|
+
: 'PowerShell BLOCKED');
|
|
72
|
+
}
|
|
73
|
+
if (diag.daemonSurvivedRecheck === false) {
|
|
74
|
+
parts.push(`daemon KILLED seconds after start (pid ${diag.daemonPid ?? '?'})`);
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
parts.push(diag.daemonAlive ? 'daemon alive' : 'daemon not running');
|
|
78
|
+
}
|
|
79
|
+
parts.push(diag.watchdogTaskInstalled ? 'watchdog installed' : 'watchdog task missing');
|
|
80
|
+
if (diag.lastCrash)
|
|
81
|
+
parts.push(`previous daemon died uncleanly at ${diag.lastCrash.lastAliveAt || 'unknown time'}`);
|
|
82
|
+
return parts.join('; ');
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Verdict for the onboarding journal: healthy unless something on this
|
|
86
|
+
* machine is actively interfering (daemon killed, PowerShell blocked, or a
|
|
87
|
+
* fresh unexplained crash). Missing watchdog alone is a warning, not
|
|
88
|
+
* interference — the daemon self-heals it at every boot.
|
|
89
|
+
*/
|
|
90
|
+
function environmentLooksHealthy(diag) {
|
|
91
|
+
if (diag.daemonSurvivedRecheck === false)
|
|
92
|
+
return false;
|
|
93
|
+
if (diag.powershellSpawnOk === false)
|
|
94
|
+
return false;
|
|
95
|
+
if (diag.lastCrash)
|
|
96
|
+
return false;
|
|
97
|
+
return true;
|
|
98
|
+
}
|
package/dist/version.json
CHANGED