keyhog 0.5.73

GPU-accelerated secret scanner for code, Git history, cloud, containers, browser assets, and live credential verification
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
//! Regression e2e: drive the REAL `keyhog` binary through the git pre-commit
//! HOOK lifecycle and the DAEMON `{start,status,stop}` lifecycle, pinning the
//! exact bytes each surface emits and the exact exit codes each transition
//! reports. Everything here shells out to `CARGO_BIN_EXE_keyhog`: no in-process
//! shortcuts, so a drift in the generated hook script, the operator messages,
//! or the exit-code contract fails a concrete assertion.
//!
//! Source of truth read while writing these:
//!   * `crates/cli/src/subcommands/hook.rs`  (HOOK_CONTENT, install/uninstall)
//!   * `crates/cli/src/subcommands/daemon.rs` (start/stop/status messages)
//!   * `crates/cli/src/daemon/trust.rs`       (already-bound refusal)
//!   * `crates/cli/src/exit_codes.rs`         (EXIT_USER_ERROR = 2)

use std::path::{Path, PathBuf};
use std::process::Command;
use tempfile::TempDir;

fn keyhog() -> PathBuf {
    PathBuf::from(env!("CARGO_BIN_EXE_keyhog"))
}

/// The EXACT bytes `keyhog hook install` writes to `.git/hooks/pre-commit`.
/// Mirrors `HOOK_CONTENT` in `crates/cli/src/subcommands/hook.rs` character for
/// character; if the shipped template drifts, `hook_install_writes_exact_bytes`
/// fails and points here.
const EXPECTED_HOOK_CONTENT: &str = concat!(
    "#!/bin/sh\n",
    "# KeyHog pre-commit hook, auto-generated by `keyhog hook install`\n",
    "#\n",
    "# If keyhog is not on PATH, block with a clear message. A missing scanner\n",
    "# means this security control did not run; letting the commit continue would\n",
    "# silently turn the installed hook into a stub.\n",
    "if ! command -v keyhog >/dev/null 2>&1; then\n",
    "    echo \"keyhog: not found on PATH - blocking commit because the pre-commit secret scan did not run.\" >&2\n",
    "    echo \"  Install keyhog (https://github.com/santhreal/keyhog), fix PATH,\" >&2\n",
    "    echo \"  or run 'keyhog hook uninstall' if this repository should not be protected.\" >&2\n",
    "    exit 127\n",
    "fi\n",
    "exec keyhog scan --fast --git-staged --backend cpu\n",
);

/// Create a real git repository at `dir` so `keyhog hook install` (which runs
/// `git rev-parse --git-dir`) accepts it. A bare `mkdir .git` is NOT enough
/// git only recognizes a directory initialized by `git init`.
fn init_git_repo(dir: &Path) {
    let out = Command::new("git")
        .arg("init")
        .arg("-q")
        .current_dir(dir)
        .output()
        .expect("spawn git init");
    assert!(
        out.status.success(),
        "git init must succeed to set up the hook test repo; stderr={}",
        String::from_utf8_lossy(&out.stderr)
    );
}

/// Run `keyhog hook <args...>` with the child cwd set to `repo` (so the
/// subcommand's `git rev-parse` resolves this repo) and `NO_COLOR` set (so the
/// styled operator messages are plain text we can byte-match).
fn run_hook(repo: &Path, args: &[&str]) -> std::process::Output {
    Command::new(keyhog())
        .current_dir(repo)
        .env("NO_COLOR", "1")
        .arg("hook")
        .args(args)
        .output()
        .expect("spawn keyhog hook")
}

fn hook_path(repo: &Path) -> PathBuf {
    repo.join(".git").join("hooks").join("pre-commit")
}

// ---------------------------------------------------------------------------
// HOOK: install / template bytes
// ---------------------------------------------------------------------------

/// `hook install` in a fresh git repo exits 0 and writes the pre-commit file
/// whose contents are byte-for-byte the shipped template.
#[test]
fn hook_install_writes_exact_bytes_and_exits_zero() {
    let dir = TempDir::new().unwrap();
    init_git_repo(dir.path());

    let out = run_hook(dir.path(), &["install"]);
    assert_eq!(
        out.status.code(),
        Some(0),
        "hook install must exit 0 on a fresh repo; stderr={}",
        String::from_utf8_lossy(&out.stderr)
    );

    let content = std::fs::read_to_string(hook_path(dir.path())).expect("read installed hook");
    assert_eq!(
        content, EXPECTED_HOOK_CONTENT,
        "installed pre-commit hook must equal the shipped template byte-for-byte"
    );
}

/// The generated hook's operative line is EXACTLY the canonical scan invocation,
/// the file starts with the POSIX shebang, and the PATH-guard exits 127. These
/// are the load-bearing lines an adopter's commit actually executes.
#[test]
fn hook_install_emits_exact_shebang_exec_and_guard_lines() {
    let dir = TempDir::new().unwrap();
    init_git_repo(dir.path());
    run_hook(dir.path(), &["install"]);

    let content = std::fs::read_to_string(hook_path(dir.path())).expect("read hook");
    let mut lines = content.lines();
    assert_eq!(
        lines.next(),
        Some("#!/bin/sh"),
        "first line must be the POSIX shebang"
    );
    assert!(
        content.contains("\nexec keyhog scan --fast --git-staged --backend cpu\n"),
        "hook must exec the canonical scan verbatim; got:\n{content}"
    );
    assert!(
        content.contains("\n    exit 127\n"),
        "missing-keyhog PATH guard must block the commit with exit 127; got:\n{content}"
    );
    // Exactly one exec line (the hook runs one scan, not several).
    assert_eq!(
        content.matches("exec keyhog ").count(),
        1,
        "hook must contain exactly one `exec keyhog` line"
    );
}

/// `hook install` marks the pre-commit file executable (all three x bits set),
/// otherwise git silently ignores it.
#[cfg(unix)]
#[test]
fn hook_install_sets_all_executable_bits() {
    use std::os::unix::fs::PermissionsExt;
    let dir = TempDir::new().unwrap();
    init_git_repo(dir.path());
    run_hook(dir.path(), &["install"]);

    let mode = std::fs::metadata(hook_path(dir.path()))
        .expect("stat hook")
        .permissions()
        .mode();
    assert_eq!(
        mode & 0o111,
        0o111,
        "hook install must OR in the executable bits (u+g+o x); mode was {mode:#o}"
    );
}

/// The install success message is the exact operator string, printed to stderr,
/// and names the pre-commit path.
#[test]
fn hook_install_success_message_is_exact() {
    let dir = TempDir::new().unwrap();
    init_git_repo(dir.path());
    let out = run_hook(dir.path(), &["install"]);

    let stderr = String::from_utf8_lossy(&out.stderr);
    assert!(
        stderr.contains("KeyHog pre-commit hook installed at"),
        "install must announce success; stderr={stderr}"
    );
    assert!(
        stderr.contains(".git/hooks/pre-commit"),
        "install message must name the pre-commit path; stderr={stderr}"
    );
    // The `--force`-only "installed/updated" wording must NOT appear on a
    // first, non-forced install.
    assert!(
        !stderr.contains("installed/updated"),
        "a plain install must say `installed`, not `installed/updated`; stderr={stderr}"
    );
}

// ---------------------------------------------------------------------------
// HOOK: idempotency / conflict / force
// ---------------------------------------------------------------------------

/// A second `hook install` over KeyHog's own hook is a no-op that exits 0 and
/// reports "already installed" (it must not error and must not rewrite bytes).
#[test]
fn hook_install_twice_reports_already_installed_exit_zero() {
    let dir = TempDir::new().unwrap();
    init_git_repo(dir.path());
    run_hook(dir.path(), &["install"]);

    let second = run_hook(dir.path(), &["install"]);
    assert_eq!(
        second.status.code(),
        Some(0),
        "re-installing over KeyHog's own hook must exit 0 (idempotent)"
    );
    let stderr = String::from_utf8_lossy(&second.stderr);
    assert!(
        stderr.contains("KeyHog pre-commit hook is already installed at"),
        "second install must report the already-installed state; stderr={stderr}"
    );
    // Bytes are still the pristine template.
    let content = std::fs::read_to_string(hook_path(dir.path())).expect("read hook");
    assert_eq!(content, EXPECTED_HOOK_CONTENT);
}

/// A KeyHog-owned hook whose body drifted from the current template (e.g. an
/// older scan line after upgrade) is rewritten without `--force` (KH-1333).
#[test]
fn hook_install_rewrites_stale_keyhog_owned_hook() {
    let dir = TempDir::new().unwrap();
    init_git_repo(dir.path());
    let stale = concat!(
        "#!/bin/sh\n",
        "# KeyHog pre-commit hook, auto-generated by `keyhog hook install`\n",
        "exec keyhog scan --git-staged --backend cpu\n",
    );
    std::fs::write(hook_path(dir.path()), stale).expect("write stale hook");

    let out = run_hook(dir.path(), &["install"]);
    assert_eq!(
        out.status.code(),
        Some(0),
        "stale KeyHog-owned hook must be rewritten; stderr={}",
        String::from_utf8_lossy(&out.stderr)
    );
    let content = std::fs::read_to_string(hook_path(dir.path())).expect("read hook");
    assert_eq!(
        content, EXPECTED_HOOK_CONTENT,
        "rewritten hook must match the current template"
    );
    let stderr = String::from_utf8_lossy(&out.stderr);
    assert!(
        stderr.contains("KeyHog pre-commit hook installed"),
        "update path must report installed/updated; stderr={stderr}"
    );
}

/// Installing over a FOREIGN (non-KeyHog) pre-commit hook without `--force` must
/// fail with EXIT_USER_ERROR (2), a message telling the user to pass `--force`,
/// and it must leave the foreign hook untouched.
#[test]
fn hook_install_refuses_foreign_hook_without_force() {
    let dir = TempDir::new().unwrap();
    init_git_repo(dir.path());
    // A pre-existing hook not written by KeyHog.
    let foreign = "#!/bin/sh\necho other-tool\n";
    std::fs::write(hook_path(dir.path()), foreign).expect("write foreign hook");

    let out = run_hook(dir.path(), &["install"]);
    assert_eq!(
        out.status.code(),
        Some(2),
        "clobbering a foreign hook without --force must exit 2 (user error); stderr={}",
        String::from_utf8_lossy(&out.stderr)
    );
    let stderr = String::from_utf8_lossy(&out.stderr);
    assert!(
        stderr.contains("a pre-commit hook already exists at") && stderr.contains("--force"),
        "refusal must name the conflict and offer --force; stderr={stderr}"
    );
    let content = std::fs::read_to_string(hook_path(dir.path())).expect("read hook");
    assert_eq!(
        content, foreign,
        "a refused install must not touch the foreign hook's bytes"
    );
}

/// `hook install --force` over a foreign hook replaces it with the KeyHog
/// template, exits 0, and reports the "installed/updated" wording.
#[test]
fn hook_install_force_replaces_foreign_hook() {
    let dir = TempDir::new().unwrap();
    init_git_repo(dir.path());
    std::fs::write(hook_path(dir.path()), "#!/bin/sh\necho other-tool\n")
        .expect("write foreign hook");

    let out = run_hook(dir.path(), &["install", "--force"]);
    assert_eq!(
        out.status.code(),
        Some(0),
        "hook install --force must exit 0; stderr={}",
        String::from_utf8_lossy(&out.stderr)
    );
    let stderr = String::from_utf8_lossy(&out.stderr);
    assert!(
        stderr.contains("KeyHog pre-commit hook installed/updated at"),
        "forced replace must report installed/updated; stderr={stderr}"
    );
    let content = std::fs::read_to_string(hook_path(dir.path())).expect("read hook");
    assert_eq!(
        content, EXPECTED_HOOK_CONTENT,
        "forced install must overwrite the foreign hook with the KeyHog template"
    );
}

// ---------------------------------------------------------------------------
// HOOK: uninstall
// ---------------------------------------------------------------------------

/// `hook uninstall` removes KeyHog's own hook, exits 0, reports the removal, and
/// the file is gone afterwards.
#[test]
fn hook_uninstall_removes_keyhog_hook() {
    let dir = TempDir::new().unwrap();
    init_git_repo(dir.path());
    run_hook(dir.path(), &["install"]);
    assert!(hook_path(dir.path()).exists());

    let out = run_hook(dir.path(), &["uninstall"]);
    assert_eq!(
        out.status.code(),
        Some(0),
        "uninstall of a KeyHog hook must exit 0; stderr={}",
        String::from_utf8_lossy(&out.stderr)
    );
    let stderr = String::from_utf8_lossy(&out.stderr);
    assert!(
        stderr.contains("KeyHog pre-commit hook removed from"),
        "uninstall must report the removal; stderr={stderr}"
    );
    assert!(
        !hook_path(dir.path()).exists(),
        "uninstall must delete the pre-commit file"
    );
}

/// `hook uninstall` when there is no hook at all exits 0 and reports that none
/// was found (a no-op, not an error).
#[test]
fn hook_uninstall_no_hook_reports_none_found_exit_zero() {
    let dir = TempDir::new().unwrap();
    init_git_repo(dir.path());

    let out = run_hook(dir.path(), &["uninstall"]);
    assert_eq!(
        out.status.code(),
        Some(0),
        "uninstall with no hook present must exit 0; stderr={}",
        String::from_utf8_lossy(&out.stderr)
    );
    let stderr = String::from_utf8_lossy(&out.stderr);
    assert!(
        stderr.contains("No pre-commit hook found at"),
        "uninstall with nothing installed must say so; stderr={stderr}"
    );
}

/// `hook uninstall` must REFUSE to delete a foreign hook: it exits 2, says the
/// hook was not installed by KeyHog, and leaves the file intact.
#[test]
fn hook_uninstall_refuses_foreign_hook() {
    let dir = TempDir::new().unwrap();
    init_git_repo(dir.path());
    let foreign = "#!/bin/sh\necho other-tool\n";
    std::fs::write(hook_path(dir.path()), foreign).expect("write foreign hook");

    let out = run_hook(dir.path(), &["uninstall"]);
    assert_eq!(
        out.status.code(),
        Some(2),
        "uninstall of a foreign hook must exit 2 (user error); stderr={}",
        String::from_utf8_lossy(&out.stderr)
    );
    let stderr = String::from_utf8_lossy(&out.stderr);
    assert!(
        stderr.contains("was not installed by KeyHog"),
        "refusal must explain KeyHog did not install it; stderr={stderr}"
    );
    let content = std::fs::read_to_string(hook_path(dir.path())).expect("read hook");
    assert_eq!(
        content, foreign,
        "a refused uninstall must not delete the foreign hook"
    );
}

/// `hook install` outside any git repository fails with EXIT_USER_ERROR (2) and
/// says it is not a git repository.
#[test]
fn hook_install_outside_git_repo_fails_user_error() {
    let dir = TempDir::new().unwrap(); // NOT a git repo
    let out = run_hook(dir.path(), &["install"]);
    assert_eq!(
        out.status.code(),
        Some(2),
        "hook install outside a git repo must exit 2 (user error); stderr={}",
        String::from_utf8_lossy(&out.stderr)
    );
    let stderr = String::from_utf8_lossy(&out.stderr);
    assert!(
        stderr.contains("not a git repository"),
        "error must name the missing-repo cause; stderr={stderr}"
    );
}

// ---------------------------------------------------------------------------
// DAEMON: start / status / stop lifecycle + error transitions
// ---------------------------------------------------------------------------

#[cfg(unix)]
fn daemon_slot() -> std::sync::MutexGuard<'static, ()> {
    static LOCK: std::sync::LazyLock<std::sync::Mutex<()>> =
        std::sync::LazyLock::new(|| std::sync::Mutex::new(()));
    // LAW10: a prior test panic must not make the independent lifecycle oracle
    // unavailable; recovering the guard still preserves process serialization.
    LOCK.lock().unwrap_or_else(|poisoned| poisoned.into_inner())
}

/// Start `keyhog daemon start --socket <sock> --backend cpu` and wait until the
/// socket is a live listener. Returns the child + socket path.
#[cfg(unix)]
fn start_daemon(dir: &Path) -> (std::process::Child, PathBuf) {
    use std::io::Read;
    use std::os::unix::net::UnixStream as StdUnixStream;
    use std::time::{Duration, Instant};

    let socket = dir.join("d.sock");
    let mut child = Command::new(keyhog())
        .args(["daemon", "start", "--socket"])
        .arg(&socket)
        .args(["--backend", "cpu"])
        .stdout(std::process::Stdio::null())
        .stderr(std::process::Stdio::piped())
        .spawn()
        .expect("spawn daemon start");

    let deadline = Instant::now() + Duration::from_secs(30);
    while Instant::now() < deadline {
        if let Some(status) = child.try_wait().expect("poll daemon process") {
            let mut stderr = String::new();
            child
                .stderr
                .take()
                .expect("daemon stderr pipe")
                .read_to_string(&mut stderr)
                .expect("read daemon stderr");
            panic!("daemon exited before readiness with {status}: {stderr}");
        }
        if socket.exists() && StdUnixStream::connect(&socket).is_ok() {
            return (child, socket);
        }
        std::thread::sleep(Duration::from_millis(100));
    }
    if child.try_wait().expect("poll timed-out daemon").is_none() {
        child.kill().expect("kill timed-out daemon");
    }
    let status = child.wait().expect("reap timed-out daemon");
    let mut stderr = String::new();
    child
        .stderr
        .take()
        .expect("daemon stderr pipe")
        .read_to_string(&mut stderr)
        .expect("read timed-out daemon stderr");
    panic!("daemon did not become ready within 30s; final status {status}: {stderr}");
}

/// Full happy path: a started daemon answers `status` with the exact status line
/// shape (exit 0), and `stop` shuts it down with the exact confirmation (exit 0)
/// and removes the socket file.
#[cfg(unix)]
#[test]
fn daemon_start_status_stop_reports_exact_lines_and_codes() {
    use sha2::{Digest, Sha256};
    use std::os::unix::fs::PermissionsExt;
    use std::time::{Duration, Instant};

    let _daemon_slot = daemon_slot();
    let dir = TempDir::new().unwrap();
    let (mut child, socket) = start_daemon(dir.path());

    // The real status client and daemon authenticate one another through peer
    // credentials. The private parent and 0600 socket are independent,
    // user-visible defenses around that authenticated connection.
    assert_eq!(
        std::fs::metadata(dir.path())
            .expect("daemon socket parent metadata")
            .permissions()
            .mode()
            & 0o777,
        0o700,
        "the daemon socket parent must remain private"
    );
    assert_eq!(
        std::fs::metadata(&socket)
            .expect("daemon socket metadata")
            .permissions()
            .mode()
            & 0o777,
        0o600,
        "the credential-streaming daemon socket must remain user-only"
    );

    let status = Command::new(keyhog())
        .args(["daemon", "status", "--socket"])
        .arg(&socket)
        .output()
        .expect("spawn daemon status");
    assert_eq!(
        status.status.code(),
        Some(0),
        "status against a live daemon must exit 0; stderr={}",
        String::from_utf8_lossy(&status.stderr)
    );
    assert_eq!(
        status.stderr, b"",
        "a current, ready daemon must not emit a stale/authentication warning"
    );

    let stdout = std::str::from_utf8(&status.stdout).expect("daemon status stdout is UTF-8");
    let lines: Vec<_> = stdout.lines().collect();
    let [warm_line, uptime_line, scope_line, policy_line, health_line] = lines.as_slice() else {
        panic!(
            "daemon status must emit exactly the five v8 operator lines in order; got:\n{stdout}"
        );
    };

    // v8 intentionally puts the warm-route identity before uptime so readiness
    // cannot be hidden below a healthy-looking process counter.
    let warm_fields: Vec<_> = warm_line.split(" · ").collect();
    let [readiness, generation_field, engine_field, binary_field, detectors_field, config_field, gpu_field] =
        warm_fields.as_slice()
    else {
        panic!("warm-backend line must contain every ordered identity field; got: {warm_line}");
    };
    assert_eq!(*readiness, "warm backend: ready");

    let generation = generation_field
        .strip_prefix("generation ")
        .expect("ordered daemon generation field");
    let generation_tail = generation
        .strip_prefix(&format!("{}-after-", child.id()))
        .expect("generation must identify this daemon process and a post-epoch start");
    let (started_ns, sequence) = generation_tail
        .split_once('-')
        .expect("generation must carry clock and sequence identities");
    let is_lower_hex = |value: &str, width: usize| {
        value.len() == width
            && value
                .bytes()
                .all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte))
    };
    assert!(
        is_lower_hex(started_ns, 32),
        "generation clock identity must be 32 lowercase hex digits; got {started_ns}"
    );
    assert_eq!(
        sequence, "0000000000000000",
        "the first warm generation in this daemon process must use sequence zero"
    );

    let engine = engine_field
        .strip_prefix("engine ")
        .expect("ordered engine identity field");
    let engine_parts: Vec<_> = engine.split(' ').collect();
    let [cli_features, scanner_features, source_features, verifier_features] =
        engine_parts.as_slice()
    else {
        panic!("engine identity must name cli/scanner/source/verifier feature sets; got {engine}");
    };
    for (part, label) in [
        (*cli_features, "cli"),
        (*scanner_features, "scanner"),
        (*source_features, "sources"),
        (*verifier_features, "verifier"),
    ] {
        let values = part
            .strip_prefix(&format!("{label}=["))
            .and_then(|value| value.strip_suffix(']'))
            .unwrap_or_else(|| panic!("engine identity field must be `{label}=[…]`; got {part}"));
        let entries: Vec<_> = values.split(',').collect();
        assert!(
            !entries.is_empty()
                && entries.iter().all(|entry| !entry.is_empty())
                && entries.windows(2).all(|pair| pair[0] < pair[1]),
            "{label} engine identities must be non-empty, unique, and sorted; got {values}"
        );
    }

    let binary_identity = binary_field
        .strip_prefix("binary ")
        .expect("ordered binary identity field");
    let executable = std::fs::read(keyhog()).expect("read tested keyhog binary");
    assert_eq!(
        binary_identity,
        format!("{:x}", Sha256::digest(executable)),
        "status must attest the exact binary that served this connection"
    );

    let detector_identity = detectors_field
        .strip_prefix("detectors ")
        .expect("ordered detector identity field");
    assert_eq!(
        detector_identity,
        keyhog_core::detector_digest(),
        "status must attest the exact embedded detector corpus loaded by the daemon"
    );

    let config_identity = config_field
        .strip_prefix("config ")
        .expect("ordered resolved-config identity field");
    assert!(
        is_lower_hex(config_identity, 16),
        "resolved-config identity must be 16 lowercase hex digits; got {config_identity}"
    );
    assert_eq!(
        *gpu_field, "GPU artifact none",
        "a forced CPU daemon must not claim a GPU artifact identity"
    );

    let embedded_detector_count = keyhog_core::load_embedded_detectors_or_fail()
        .expect("load embedded detector count")
        .len();
    let uptime_suffix =
        format!("s · 0 scans served · 0 active · {embedded_detector_count} detectors");
    let uptime = uptime_line
        .strip_prefix("keyhog daemon: uptime ")
        .and_then(|line| line.strip_suffix(&uptime_suffix))
        .and_then(|seconds| seconds.parse::<u64>().ok())
        .unwrap_or_else(|| panic!("uptime line must carry exact idle counters; got {uptime_line}"));
    assert!(
        uptime <= 30,
        "a newly ready daemon must report a fresh uptime, got {uptime}s"
    );
    assert_eq!(
        *scope_line,
        "scan scope: warm stdin/single-file requests only; start with --mass for bounded source transactions. Warm daemon requests return before baseline, Merkle state, verification, lockdown, and per-request scanner policy; those post-steps run in-process."
    );
    assert_eq!(
        *policy_line,
        "backend policy: forced cpu-fallback (daemon startup diagnostic override)"
    );
    assert_eq!(
        *health_line, "backend health: no recovered runtime faults",
        "an idle forced-CPU daemon must report the exact clean recovery state"
    );
    assert!(
        stdout.ends_with('\n'),
        "daemon status must terminate its final operator line"
    );

    let stop = Command::new(keyhog())
        .args(["daemon", "stop", "--socket"])
        .arg(&socket)
        .output()
        .expect("spawn daemon stop");
    assert_eq!(
        stop.status.code(),
        Some(0),
        "daemon stop must exit 0; stderr={}",
        String::from_utf8_lossy(&stop.stderr)
    );
    assert_eq!(
        stop.stdout, b"",
        "daemon stop must reserve stdout for machine-readable command output"
    );
    assert_eq!(
        stop.stderr, b"keyhog daemon stopped\n",
        "daemon stop must print exactly one confirmation line"
    );

    let child_status = child.wait().expect("reap stopped daemon");
    assert_eq!(
        child_status.code(),
        Some(0),
        "a confirmed graceful stop must make the daemon process exit 0"
    );
    let deadline = Instant::now() + Duration::from_secs(10);
    while socket.exists() && Instant::now() < deadline {
        std::thread::sleep(Duration::from_millis(50));
    }
    assert!(
        !socket.exists(),
        "daemon stop must remove the socket so a later start does not refuse it"
    );
}

/// A SECOND `daemon start` on a socket already bound by a live daemon must fail
/// closed with EXIT_USER_ERROR (2) and the exact "already bound" refusal, it
/// must not clobber the running daemon's socket.
#[cfg(unix)]
#[test]
fn daemon_second_start_refuses_already_bound_socket() {
    let _daemon_slot = daemon_slot();
    let dir = TempDir::new().unwrap();
    let (mut child, socket) = start_daemon(dir.path());

    let second = Command::new(keyhog())
        .args(["daemon", "start", "--socket"])
        .arg(&socket)
        .args(["--backend", "cpu"])
        .output()
        .expect("spawn second daemon start");
    assert_eq!(
        second.status.code(),
        Some(2),
        "a second start on a live socket must exit 2 (user error), not clobber; stderr={}",
        String::from_utf8_lossy(&second.stderr)
    );
    let stderr = String::from_utf8_lossy(&second.stderr);
    assert!(
        stderr.contains("is already bound by another keyhog daemon"),
        "second start must report the already-bound refusal; stderr={stderr}"
    );
    assert!(
        stderr.contains("keyhog daemon stop"),
        "refusal must tell the operator to stop the existing daemon first; stderr={stderr}"
    );

    // The original daemon is still alive and answering.
    let status = Command::new(keyhog())
        .args(["daemon", "status", "--socket"])
        .arg(&socket)
        .output()
        .expect("status after refused second start");
    assert_eq!(
        status.status.code(),
        Some(0),
        "the first daemon must still be serving after the refused second start"
    );

    let _ = Command::new(keyhog())
        .args(["daemon", "stop", "--socket"])
        .arg(&socket)
        .output();
    let _ = child.wait();
}

/// `daemon stop` when no daemon is running fails with EXIT_USER_ERROR (2) and a
/// message naming the missing socket and the "already stopped?" hint.
#[cfg(unix)]
#[test]
fn daemon_stop_without_daemon_exits_user_error() {
    let dir = TempDir::new().unwrap();
    let socket = dir.path().join("absent.sock"); // never created

    let out = Command::new(keyhog())
        .args(["daemon", "stop", "--socket"])
        .arg(&socket)
        .output()
        .expect("spawn daemon stop");
    assert_eq!(
        out.status.code(),
        Some(2),
        "stop with no daemon must exit 2 (user error); stderr={}",
        String::from_utf8_lossy(&out.stderr)
    );
    let stderr = String::from_utf8_lossy(&out.stderr);
    assert!(
        stderr.contains("daemon stop: no daemon at") && stderr.contains("already stopped"),
        "stop must report no-daemon with the already-stopped hint; stderr={stderr}"
    );
}

/// `daemon status` when no daemon is running fails with EXIT_USER_ERROR (2) and
/// tells the operator to start one.
#[cfg(unix)]
#[test]
fn daemon_status_without_daemon_exits_user_error() {
    let dir = TempDir::new().unwrap();
    let socket = dir.path().join("absent.sock"); // never created

    let out = Command::new(keyhog())
        .args(["daemon", "status", "--socket"])
        .arg(&socket)
        .output()
        .expect("spawn daemon status");
    assert_eq!(
        out.status.code(),
        Some(2),
        "status with no daemon must exit 2 (user error); stderr={}",
        String::from_utf8_lossy(&out.stderr)
    );
    let stderr = String::from_utf8_lossy(&out.stderr);
    assert!(
        stderr.contains("daemon status: no daemon at") && stderr.contains("keyhog daemon start"),
        "status must report no-daemon and how to start one; stderr={stderr}"
    );
}