openlatch-client 0.5.3

OpenLatch runtime enforcement node — the capture-and-enforce adapter that evaluates every covered action against a coding agent's Autonomy Zone before it runs
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
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
//! Cline's binding — the agent this build **installs into, captures from, and
//! enforces through when, and only when, its plugin is there**.
//!
//! Beside `claude_code.rs` and `codex_cli.rs` because every shipped
//! `impl AgentBinding` lives here; the path resolvers stay in
//! [`crate::hooks::cline`], mirroring the same split the other two use.
//!
//! Everything about this file used to follow from one line —
//! [`AgentBinding::installable`] answering `false`, which made an empty
//! `hook_event_types`, an empty `load_bearing_events` and an unknowable
//! `liveness` the only honest answers available. That precondition is gone:
//! [`crate::hooks::hook_files`] writes Cline's ten hook scripts and
//! [`AgentBinding::hook_surface`] returns [`HookSurface::Directory`], so every
//! shared path that would once have run a JSON rewrite against a directory now
//! has to write its own arm instead.
//!
//! What has **not** changed is that the ten file shims deliver no verdict: a
//! `cancel` forwarded through Cline's file-hook lane raises
//! `ControlledStopError` and aborts the developer's whole task, so they print
//! `{}` whatever the daemon answered. Enforcement rides a different artefact
//! entirely — the plugin at [`AgentBinding::plugin_surface`], whose
//! `beforeTool` returns `{skip, reason}` — and [`crate::hook_output::cline`] is
//! the translator that renders into it.
//!
//! **So this binding's enforcement answers are a fact about the host, not a
//! constant.** [`crate::hooks::cline_plugin::enforcement_surface`] is the one
//! detector, consulted in [`ClineBinding::detect`] and kept on the struct:
//! [`AgentBinding::liveness`] answers `Some(true)` when it says `plugin`, and
//! `Some(false)` — carrying [`crate::error::ERR_CLINE_NOT_ENFORCING`] — for the
//! other two, while [`AgentBinding::capabilities`]' `expressible` widens on
//! exactly that same condition. An install with no plugin, or one the developer
//! has switched off in `disabledPlugins`, captures everything and refuses
//! nothing, and says so.

use std::path::{Path, PathBuf};

use serde_json::Value;

use crate::core::hook_state::marker::OpenlatchMarker;

use super::super::binding::{
    AgentBinding, BindingCapabilities, DaemonChannel, FailureMode, HookSurface, LivenessReport,
    ModelRelayWiring,
};

/// The directory Cline's VS Code lane discovers hooks under, inside the
/// user-asset root.
///
/// **Re-exported, not re-spelled.** This file used to carry its own `"Hooks"`
/// literal and a test in `hooks::cline` to hold the two copies together; the
/// name is one fact and it belongs to the module that owns Cline's path
/// resolution. Two spellings of one directory is the trap
/// [`crate::hooks::cline::STORE_DIR_ENV`] documents — the store root holds a
/// lowercase `hooks/` which is a **different directory**, and a probe that
/// disagrees with the installer about which is which reports a confident
/// `absent` against a directory that is right there.
use crate::hooks::cline::ASSET_HOOKS_DIR_NAME as HOOKS_DIR_NAME;

/// Cline, detected and — for its HOOK surface — not installable.
///
/// **Paths are still resolved live** through [`crate::hooks::cline`], which is
/// the module that owns path resolution (D-01) and the one place the three
/// seams are honoured. Caching a path would be a second answer that can
/// disagree with the probe.
///
/// **Provider selection is not cached, because nothing here needs it.** Cline
/// carries many providers at once, and every one it has configured is wired to
/// its own relay endpoint by the daemon's wiring pass, which reads Cline's files
/// itself ([`crate::hooks::cline_providers`]). The binding only says that this
/// agent HAS provider slots.
pub struct ClineBinding {
    /// Which lane could refuse a tool call when this binding was constructed.
    ///
    /// **Cached, and settled here.** [`AgentBinding::capabilities`] takes `&self` and returns a
    /// `&'static [&'static str]`, so it cannot stat anything — the decision has
    /// to be made somewhere that can, and [`Self::detect`] already stats the
    /// store. Making the trait method fallible or path-taking to move the
    /// decision into it would change every binding to serve one.
    ///
    /// The staleness is the same bounded kind: every loop that consults a
    /// binding calls `detect_agents()` at the top of its tick, so a plugin
    /// installed, removed or switched off is picked up on the next one.
    enforcement: crate::hooks::cline_plugin::EnforcementSurface,
}

impl ClineBinding {
    /// This binding's CloudEvents `source` value, reachable without a binding.
    ///
    /// `agent_type()` needs a `&self`, and constructing one for the sake of
    /// reading a constant would resolve the active provider — a file read — to
    /// answer a question that does not depend on it. One definition:
    /// [`AgentBinding::agent_type`] returns this.
    pub const AGENT_TYPE: &'static str = "cline";

    /// A binding with no request plane and no enforcement surface, for the
    /// answers that depend on neither.
    ///
    /// Every method except `model_relay_wiring`, `liveness` and `capabilities`
    /// resolves live through [`crate::hooks::cline`] and is unaffected by which
    /// provider is active, so a test about `installable()` or
    /// `hook_config_path()` has no business seeding a `providers.json` to get
    /// one. Test-only: production always goes through [`Self::detect`], which is
    /// what resolves both.
    ///
    /// `EnforcementSurface::None` is the honest default here rather than a
    /// convenience — a binding nobody detected has no plugin behind it, and the
    /// two methods that read this field are tested against a *detected* binding
    /// over a real tempdir tree, never against this one.
    #[cfg(test)]
    pub(crate) fn detached() -> Self {
        Self {
            enforcement: crate::hooks::cline_plugin::EnforcementSurface::None,
        }
    }

    /// `Some` when Cline's store root exists on this host.
    ///
    /// Stats the **store** root and nothing else, which is the same shape
    /// `claude_code::detect` and `codex_cli::detect` use: the directory the
    /// agent creates on first run is what "installed" means, and a root that
    /// resolves but is not there is not an install.
    ///
    /// It is also what makes every existing fixture in this crate safe.
    /// [`crate::hooks::cline::absent_seams`] points all three seams at paths it
    /// never creates, so this answers `None` under them and arming detection
    /// changed no fixture's agent count —
    /// `detect_returns_none_when_seam_path_absent` is the assertion behind that
    /// claim.
    pub fn detect() -> Option<Self> {
        let (store, _located_by) = crate::hooks::cline::store_root()?;
        if !store.is_dir() {
            return None;
        }
        // Resolved HERE: `capabilities()` has no `&Path` and no `Result` to
        // work with, so the filesystem question behind it can only be asked
        // somewhere that has already stat-ed the store. This is that place.
        let enforcement = crate::hooks::cline::enforcement_surface();
        Some(Self { enforcement })
    }
}

impl AgentBinding for ClineBinding {
    fn agent_type(&self) -> &'static str {
        // Already in `SCHEMA_AGENT_TYPES`, so this binding needs no schema
        // change: the wire vocabulary has always been wider than the set of
        // agents a build can detect.
        Self::AGENT_TYPE
    }

    fn display_name(&self) -> &'static str {
        "Cline"
    }

    fn config_dir(&self) -> PathBuf {
        // NOT `store_root()?.0` — this returns a `PathBuf`, not an `Option`, so
        // `?` does not compile here.
        //
        // `unwrap_or_default()` yields an empty path, and it is unreachable in
        // practice: `detect()` above already resolved and stat-ed this root, so
        // no `ClineBinding` exists on a host where it answers `None`. It is
        // written rather than asserted because a panic in a binding accessor
        // would fire inside `doctor` — the command whose whole job is to keep
        // working on a broken host.
        crate::hooks::cline::store_root()
            .map(|(path, _)| path)
            .unwrap_or_default()
    }

    fn hook_config_path(&self) -> PathBuf {
        // The trait says "the FILE hook registrations are written into". Cline
        // has no such file: the VS Code lane discovers hooks as executables in
        // the asset root's `Hooks/` DIRECTORY. Naming the directory is the only
        // honest answer, and the honest answer is a hazard — `Path::exists()`
        // is true for a directory, so every caller that treats this as a file
        // (`doctor`'s `inspect_file`, `remove_hooks`' early return,
        // `doctor --rescue`'s ZIP collection) sails past its own guard and
        // reads, rewrites or exfiltrates a directory.
        //
        // `installable()` used to be what stopped that, and it no longer is:
        // it answers `true`. [`Self::hook_surface`] below is, and it is a
        // stronger guard than the flag ever was — a caller cannot reach this
        // path through it without writing a `Directory` arm.
        crate::hooks::cline::asset_root()
            .unwrap_or_default()
            .join(HOOKS_DIR_NAME)
    }

    fn hook_surface(&self) -> HookSurface {
        // The one override of the default, and it says in a variant what the
        // comment above has to say in prose: this path is a DIRECTORY of
        // executable hook scripts. Shared code that `match`es on this can no
        // longer reach a JSON rewrite, a `read_to_string` or a support-bundle
        // collection with it by accident — it has to write the directory arm.
        HookSurface::Directory(self.hook_config_path())
    }

    fn plugin_surface(&self) -> Option<PathBuf> {
        // Cline's ENFORCEMENT surface, and it is not where the ten live.
        //
        // The ten hook scripts go under the **user-asset** root's `Hooks/`;
        // this goes under the **store** root, because that is the only place
        // Cline's plugin search looks — `join(resolveClineDir(), "plugins")`
        // (`paths.ts:567`, v4.1.17). Pointing it at the asset root would write
        // a file nothing ever loads, on a host that then reports enforcement.
        //
        // The DIRECTORY, not the file: its name is the plugin id, which is what
        // `disabledPlugins` in `global-settings.json` holds. The artefact is
        // `cline_plugin::entry_path` of this path.
        //
        // Resolved live through `crate::hooks::cline`, like every other path on
        // this binding — the module that owns path resolution (D-01) and the one
        // place the three seams are honoured. `None` only when there is no home
        // directory to fall back to, which is the same condition that makes
        // `detect` answer `None`.
        //
        // DELEGATED rather than composed here, so the installer's directory and
        // the reporter's are one string: `liveness()` and the attestation's
        // `enforcement_surface` both hang off this same resolver, and a second
        // `.join()` here is how a host comes to report on a directory the
        // installer never wrote.
        crate::hooks::cline::plugin_dir()
    }

    fn installable(&self) -> bool {
        // THE line this whole file used to follow from, and the one that moved.
        //
        // It answered `false` while there was nothing that could write this
        // surface: `install_hooks` had one path, a JSONC rewrite of a settings
        // file, and Cline's surface is a directory of executable scripts. An
        // install then meant creating `<asset root>/Hooks` and registering
        // nothing in it.
        //
        // Both halves of that are now built. [`HookSurface::Directory`] sends
        // this binding to `hooks::install_hook_files`, which writes the ten
        // shims `hook_files::CLINE_HOOK_FILES` names, and `DaemonChannel` is
        // `OpenlatchDirArg`, so no bearer token reaches the asset root either.
        //
        // **Installable is not enforceable**, and this method is not the place
        // that claims otherwise — see `capabilities()` and `liveness()` below,
        // which both still say no verdict is deliverable.
        true
    }

    fn hook_event_types(&self) -> &'static [&'static str] {
        // THE ten, and the same array the writer installs — never a second
        // spelling of it. This list is what `health::inspect_directory` checks
        // the directory against, what `doctor --rescue` collects and what the
        // reconciler walks, so a list that disagreed with the writer's would
        // make doctor report a file that was never registered, or miss one
        // that was.
        //
        // A directory surface registers by FILE NAME, which is why the writer
        // owns the list: there is no JSON entry here for an event type to key.
        // The names are Cline's `HookConfigFileName` spellings; the per-host
        // file name (`PreToolUse` or `PreToolUse.ps1`) comes off
        // `hook_files::hook_file_name`.
        &crate::hooks::hook_files::CLINE_HOOK_FILES
    }

    fn load_bearing_events(&self) -> &'static [&'static str] {
        // The three whose absence means capture is broken, judged on CLINE's
        // set and never on Claude Code's: the tool call about to run, the
        // developer's prompt, and the start of the task the other two belong
        // to.
        //
        // Deliberately not all ten. Four of the ten fire in one of Cline's two
        // lanes and `PreCompact` fires in neither — `hook_files` documents the
        // table — so judging the install on every file would put a permanent
        // red on a working VS Code host. These three fire in both lanes.
        &["PreToolUse", "UserPromptSubmit", "TaskStart"]
    }

    fn daemon_channel(&self) -> DaemonChannel {
        // `OpenlatchDirArg`, never `EnvVars`, and the choice is a safety
        // property rather than a preference: `install_hooks` writes the bearer
        // token's VALUE into the agent's config for an `EnvVars` channel, and
        // only a path for this one. No daemon token may ever reach Cline's
        // asset root — and that day has arrived: `installable()` is `true` and
        // this build writes ten executable scripts into it. The scripts pass
        // `--openlatch-dir` and read both secrets from that directory, so the
        // token stays where it was (D-09).
        DaemonChannel::OpenlatchDirArg
    }

    fn liveness(&self) -> LivenessReport {
        // **THE one place this host says whether it enforces Cline**, and it
        // answers from the detector rather than from a constant.
        //
        // It has been both constants in turn, each right at the time: `None`
        // while this build installed nothing ("arming is a claim about a hook
        // we installed"), then `Some(false)` once the ten shims landed and
        // could refuse nothing. The plugin is what moves it again — an artefact
        // that can refuse a single tool call, which is the fact `armed` was
        // always meant to be about.
        //
        // The three arrangements, and the two that are NOT green:
        //
        //   * `plugin`   — ours, present, not switched off: `Some(true)`.
        //   * `disabled` — ours, present, on `disabledPlugins`. The developer
        //     turned it off; that is their switch and re-enabling it is not
        //     ours to do, so the remedy names the switch and the check stays
        //     red. *Off is never a pass.*
        //   * `none`     — nothing of ours at the entry path, or a file there
        //     that is somebody else's and was left alone. `init` is the remedy.
        //
        // Both of those carry `OL-1410` and **no second code is allocated**:
        // one condition — this host is not enforcing — gets one code, and
        // `enforcement_surface` in `doctor --json` is the structured *why*.
        // Two codes for one condition is the second detector the
        // one-question-one-set-of-detectors invariant forbids.
        //
        // `detail` names capturing and enforcing separately either way, because
        // those are the two claims a reader of this row is trying to tell
        // apart.
        use crate::hooks::cline_plugin::EnforcementSurface;
        match self.enforcement {
            EnforcementSurface::Plugin => LivenessReport {
                armed: Some(true),
                detail: Some(
                    "Cline's ten hook scripts capture every event, and the OpenLatch plugin \
                     refuses a denied tool call on its own — the one lane that stops a single \
                     call without aborting the developer's task."
                        .to_string(),
                ),
                // Neither is rendered for a green check, and neither is
                // invented to fill the struct: an `Enforced` row has nothing
                // to act on.
                remedy: None,
                code: None,
            },
            EnforcementSurface::Disabled => LivenessReport {
                armed: Some(false),
                detail: Some(
                    "OpenLatch's Cline plugin is installed and listed in `disabledPlugins`, so \
                     Cline never loads it: the ten hook scripts still capture every event and \
                     no tool call can be refused."
                        .to_string(),
                ),
                remedy: Some(
                    "Remove \"openlatch\" from `disabledPlugins` in Cline's \
                     `global-settings.json` — OpenLatch never edits that list, because it is \
                     your switch — then run `openlatch doctor` again."
                        .to_string(),
                ),
                code: Some(crate::error::ERR_CLINE_NOT_ENFORCING),
            },
            EnforcementSurface::None => LivenessReport {
                armed: Some(false),
                detail: Some(
                    "Cline's ten hook scripts post every event and discard the verdict — a deny \
                     returned through its file-hook lane aborts the developer's whole task \
                     rather than the one tool call — and the plugin that can refuse one is not \
                     installed."
                        .to_string(),
                ),
                remedy: Some(
                    "Run `openlatch init --agent cline` to install the enforcement plugin. If a \
                     file that is not ours already sits at `plugins/openlatch/index.js`, \
                     OpenLatch leaves it alone: move it aside first."
                        .to_string(),
                ),
                code: Some(crate::error::ERR_CLINE_NOT_ENFORCING),
            },
        }
    }

    fn build_hook_entry(
        &self,
        _event: &str,
        _binary: &Path,
        _port: u16,
        _marker: &OpenlatchMarker,
    ) -> Value {
        // An inert object, NOT `unreachable!()`.
        //
        // CORRECTED 2026-09-14 by adversarial review, which disproved the
        // "unreachable by construction" claim this method previously carried.
        // It named two callers and asserted both were guarded. They are not:
        //   * `hooks::install_hooks` IS guarded (`hooks/mod.rs:365`).
        //   * `daemon::reconciler::compute_field_deltas` (`reconciler/mod.rs:571`)
        //     is NOT. The reconciler's `installable()` guard sits in `try_heal`
        //     (`:385`), a different function; the drift diff reaches the builder
        //     without passing it.
        //
        // That path is driven by PERSISTED STATE plus FILESYSTEM CONTENTS — a
        // tracked entry whose marker has drifted, against a hook path that reads
        // as a file. Both are fallible, attacker-adjacent input, which makes a
        // panic here an error condition under `.claude/rules/error-handling.md`,
        // not an impossible internal invariant. `FakeBinding::capabilities`'s
        // `unimplemented!()` is a TEST fixture and does not license a panic in a
        // shipped binding.
        //
        // An empty object is still the honest answer now that this build DOES
        // install Cline: this binding registers by file name, not by JSON
        // entry, so there is no entry shape for a field diff to compare
        // against. The `Directory` arm of `reconcile_target` is what verifies
        // Cline — descriptor sha256 per file — and never reaches here; a
        // `ConfigFile`-shaped diff that somehow did finds every field missing
        // and reports drift it cannot heal, which is true. The write protection
        // is the surface variant, not this return value.
        serde_json::json!({})
    }

    fn config_is_machine_global(&self) -> bool {
        // Delegate, never re-derive — the same rule `claude_code.rs:127-130`
        // and `codex_cli.rs:309-310` state, for the same reason.
        //
        // CORRECTED 2026-09-14 by adversarial review. This returned a hardcoded
        // `false`, justified as "this binding is never written to, so the
        // question does not arise". That is wrong twice over: `~/.cline` IS
        // machine-global in the sense the trait means — every Cline session on
        // the host shares it — and the question DOES arise, one initiative
        // earlier than the old comment claimed. The single production reader is
        // the daemon's wiring-ownership guard, which is skipped today only
        // because `model_relay_wiring()` is `None`; I-2 gives Cline a wiring
        // convention and un-skips it. `false` would then read as "relocated,
        // safe to own" against the developer's real store.
        crate::hooks::cline::config_is_machine_global()
    }

    fn capabilities(&self) -> BindingCapabilities {
        BindingCapabilities {
            // **A function of the host, which is the only honest shape for it.**
            // A static answer here would let a file-only install advertise
            // `deny` while every shim discards the verdict — the precise
            // dishonesty this unit exists to avoid — so the slice is chosen
            // from the enforcement surface `detect()` observed.
            //
            // `&[]` when there is no plugin, and emphatically not "allow only":
            // an empty set means there is no channel at all, and the
            // degradation ladder must never find a tier here to silently fall
            // back to.
            //
            // **`ask` is absent from BOTH slices.** Cline's `ask` degrades to
            // allow-and-flag, and it is this field that records the
            // degradation: the candidate channel, `review: true`, is
            // `HookControl` — the file lane, which this build never denies on —
            // and **OQ-3.5** has not settled whether it surfaces an approval
            // prompt at all. Claiming a native Ask would express a tier into a
            // lane that discards it. Cline's own degradation is stated here;
            // the cross-agent expressiveness matrix lives on the Agent Coverage
            // page (`03-Product/Agent Coverage`) and is never copied into this
            // tree, where a second copy would go stale the first time one agent
            // moved.
            expressible: if self.enforcement.is_enforcing() {
                // What the plugin lane really delivers: `{skip, reason}` is a
                // refusal, and `undefined` is the tool running normally.
                &["allow", "deny"]
            } else {
                &[]
            },
            // `false`, and it stays false now that a lane exists. Cline's
            // `AgentBeforeToolResult` has an `input` field that could carry a
            // rewrite — nothing delivers one: the plugin forwards Cline's own
            // parameters untouched, by design (one re-typing, daemon-side), and
            // `hook_output::cline` renders `optimize` as `{}`. A `true` here
            // would advertise a rewrite nothing performs.
            can_mutate_arguments: false,
            // K-1, settled by the owner 2026-09-10. `Unknown` is documented
            // "Undocumented, or not observed", and it stays: what this field
            // means is what the AGENT does when our hook itself fails, and that
            // is still unobserved. The ten shims print `{}` whatever happens,
            // and the plugin never throws — its `beforeTool` catches everything
            // and returns `undefined` — so neither lane has yet put Cline in a
            // position to fail open or closed over us. That is what the variant
            // is for, not a shrug.
            //
            // Nothing rides on the value: `native_failure_mode` has zero
            // production readers tree-wide, and two writers, both `FailOpen`.
            //
            // The trait gap is real. Cline's own behaviour is a fourth thing
            // this enum cannot express — a file hook's `cancel: true` becomes
            // `stop: true` and raises `ControlledStopError`, which kills the
            // whole task, and a plugin that throws kills the run. Neither
            // fail-open nor fail-closed, and it is why the shims never forward
            // a verdict and why the plugin catches. Do NOT add a variant here
            // for it: it would describe how OUR artefacts behave, which is a
            // fact about this crate rather than about the agent.
            native_failure_mode: FailureMode::Unknown,
            // The asset root is the user's own documents directory, not an
            // administrative layer like Codex's `requirements.toml`.
            admin_owned_settings: false,
            // UNSOURCED, deliberately: the PRD carries no session-declaration
            // property for Cline, and I-2's request plane is what will observe
            // the truth. `false` is the safe answer — it only ever widens what
            // attribution has to fall back to. Do not cite the PRD for it.
            declares_session_in_request: false,
        }
    }

    fn model_relay_wiring(&self) -> Option<ModelRelayWiring> {
        // No single slot on the main relay port. Cline carries many providers
        // at once, each reading its base URL from its own key, and two of them
        // speaking one protocol to different hosts cannot share a port that
        // routes by protocol — so every configured provider gets its own
        // endpoint instead ([`Self::provider_endpoints`]).
        None
    }

    fn provider_endpoints(
        &self,
    ) -> Option<&'static dyn crate::hooks::provider_endpoints::ProviderEndpoints> {
        static ENDPOINTS: crate::hooks::cline_providers::ClineProviderEndpoints =
            crate::hooks::cline_providers::ClineProviderEndpoints::RESOLVED;
        Some(&ENDPOINTS)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    /// The whole point of the binding, asserted where it is declared rather
    /// than only where it is consumed.
    ///
    /// The inverse of what it asserted through I-1. `installable()` is what
    /// every mutation path reads, so this is the single fact that turns the
    /// whole capture lane on — and the one to revert first if it has to come
    /// back off (run `openlatch uninstall` BEFORE reverting it, or the scripts
    /// are orphaned on the host with nothing left that will remove them).
    #[test]
    fn cline_declares_it_can_be_installed_into() {
        assert!(
            ClineBinding::detached().installable(),
            "Cline's hook surface IS written by this build — ten executable \
             shims into the asset root's Hooks/ directory"
        );
    }

    /// **Re-pointed, not deleted** — this was
    /// `the_empty_answers_are_tied_to_non_installability`, and it is still the
    /// alarm that these answers move as one set rather than one at a time.
    ///
    /// Three of the four moved together, because they are one fact: this build
    /// writes Cline's hook surface, so it registers events and can name the
    /// ones whose absence means capture is broken. An empty `hook_event_types`
    /// on an installable binding is not a no-op — for a `ConfigFile` agent it
    /// creates the settings file, writes the daemon token and reports success,
    /// and for this one it makes `health::inspect_directory` check nothing and
    /// call the result unhealthy.
    ///
    /// **`expressible` deliberately did NOT move with them, and that is the
    /// assertion this test exists to hold.** Installing is not enforcing: the
    /// three answers above are true of every host this build installed into,
    /// and `expressible` is true only of a host that also has the plugin. This
    /// binding is `detached()` — nothing detected it, so it has no plugin
    /// behind it — and the empty slice is what that host can express.
    /// `enforcement_answers_follow_the_plugin` is the other direction.
    ///
    /// **Wiring is not on this list.** Hooks and wiring are independent
    /// surfaces; asserting them together here is what would make the next edit
    /// to one of them look like a regression in the other.
    #[test]
    fn the_answers_are_tied_to_installability() {
        let binding = ClineBinding::detached();
        assert!(binding.installable());

        // The writer's list, not a second spelling of it: doctor, rescue and
        // the reconciler all walk this, and a list that disagreed with
        // `write_all` would report a file nobody registered.
        assert_eq!(
            binding.hook_event_types(),
            crate::hooks::hook_files::CLINE_HOOK_FILES,
            "the binding must register exactly what the writer installs"
        );

        // Cline's set, never Claude Code's, and a strict subset of the ten:
        // four of the ten fire in only one of Cline's lanes and `PreCompact`
        // fires in neither, so judging the install on all ten would red a
        // working host.
        assert_eq!(
            binding.load_bearing_events(),
            ["PreToolUse", "UserPromptSubmit", "TaskStart"],
        );
        for event in binding.load_bearing_events() {
            assert!(
                binding.hook_event_types().contains(event),
                "{event} is load-bearing and is not registered — it would be \
                 reported missing on every host"
            );
        }

        assert!(
            binding.capabilities().expressible.is_empty(),
            "installing is not enforcing: `expressible` widens for a host whose \
             plugin is installed and enabled, and not for one that merely has \
             Cline's ten hook scripts"
        );
    }

    /// No daemon token may reach Cline's asset root. `install_hooks` writes the
    /// token's *value* into the agent's config for an `EnvVars` channel and
    /// only a directory path for this one.
    #[test]
    fn the_daemon_channel_never_carries_a_token() {
        assert!(
            matches!(
                ClineBinding::detached().daemon_channel(),
                DaemonChannel::OpenlatchDirArg
            ),
            "an EnvVars channel would put the bearer token in plaintext in a file we \
             have no business writing at all"
        );
    }

    /// `armed: Some(false)` on a host with no plugin — a standing red, and
    /// meant to be.
    ///
    /// It answered `None` while we installed nothing, then a constant
    /// `Some(false)` once the ten shims landed; the plugin is what made it a
    /// question about the host rather than a constant at all. This case holds
    /// the **no-plugin** end: the code and the remedy are *mandatory* in this
    /// state — `Check::validate` rejects a failed check missing either — so
    /// they are asserted here, at the binding, rather than only where `doctor`
    /// renders them.
    #[test]
    fn liveness_is_monitored_rather_than_unknowable() {
        let report = ClineBinding::detached().liveness();
        assert_eq!(report.armed, Some(false));
        assert_eq!(report.code, Some(crate::error::ERR_CLINE_NOT_ENFORCING));
        assert!(
            report.remedy.is_some(),
            "a Some(false) liveness without a remedy fails Check::validate"
        );
        assert!(
            report.detail.is_some(),
            "capturing and enforcing are two claims, and the detail is what \
             tells them apart for a reader of the Monitored row"
        );
    }

    /// **Both directions**, over a real tree: the plugin is what moves
    /// `liveness()` and `expressible`, and the developer's switch moves them
    /// back.
    ///
    /// Written against `detect()` rather than a hand-built struct, because
    /// `detect()` is the one place the decision is made — a test that set the
    /// field directly would pass on a build that had stopped consulting the
    /// detector at all.
    ///
    /// The four arrangements, in order: no plugin, plugin, plugin +
    /// `disabledPlugins`, plugin removed again. Every path is inside a
    /// `tempdir()` reached through the three seams, so nothing here can name
    /// the developer's `~/.cline` — which holds `data/secrets.json`, plaintext
    /// API keys.
    #[test]
    fn enforcement_answers_follow_the_plugin() {
        let _lock = crate::hooks::claude_code::CONFIG_DIR_ENV_LOCK
            .lock()
            .unwrap_or_else(|e| e.into_inner());
        let _seam_lock = crate::hooks::cline::SEAM_ENV_LOCK
            .lock()
            .unwrap_or_else(|e| e.into_inner());

        let root = tempfile::tempdir().expect("temp dir");
        let store = root.path().join("store");
        let data = store.join("data");
        let settings = data.join("settings");
        std::fs::create_dir_all(&settings).expect("seed the store and its settings dir");
        let _seams = crate::hooks::cline::EnvOverride::apply([
            (
                crate::hooks::cline::STORE_DIR_ENV,
                Some(store.clone().into_os_string()),
            ),
            (
                crate::hooks::cline::DATA_DIR_ENV,
                Some(data.clone().into_os_string()),
            ),
            (
                crate::hooks::cline::ASSETS_DIR_ENV,
                Some(root.path().join("assets").into_os_string()),
            ),
        ]);

        // 1. Installed Cline, no plugin of ours. Capturing, enforcing nothing.
        let binding = ClineBinding::detect().expect("the store root exists");
        assert!(
            binding.capabilities().expressible.is_empty(),
            "a file-only install must advertise no verdict channel at all"
        );
        assert_eq!(binding.liveness().armed, Some(false));

        // 2. The plugin, written by the installer's own writer — never a
        //    hand-rolled body, or this would pass against a marker the real
        //    `is_ours` rejects.
        let plugin_dir = binding.plugin_surface().expect("Cline has a plugin lane");
        crate::hooks::cline_plugin::install(&plugin_dir, root.path()).expect("install the plugin");

        let armed = ClineBinding::detect().expect("the store root still exists");
        assert_eq!(
            armed.capabilities().expressible,
            &["allow", "deny"],
            "a verified plugin is the one thing that widens this"
        );
        let report = armed.liveness();
        assert_eq!(report.armed, Some(true));
        assert!(
            report.code.is_none() && report.remedy.is_none(),
            "an Enforced row has nothing to act on and no code to carry"
        );

        // 3. The developer switches it off. Our file is untouched and correct;
        //    Cline will not load it, so the claim has to come back down.
        std::fs::write(
            settings.join("global-settings.json"),
            format!(
                r#"{{"disabledPlugins":["{}"]}}"#,
                crate::hooks::cline_plugin::PLUGIN_ID
            ),
        )
        .expect("seed the developer's switch");

        let disabled = ClineBinding::detect().expect("the store root still exists");
        assert!(
            disabled.capabilities().expressible.is_empty(),
            "a plugin Cline never loads delivers nothing, however correct the file is"
        );
        let report = disabled.liveness();
        assert_eq!(report.armed, Some(false));
        assert_eq!(
            report.code,
            Some(crate::error::ERR_CLINE_NOT_ENFORCING),
            "one condition — not enforcing — carries one code, whatever the reason"
        );
        assert!(
            report
                .remedy
                .as_deref()
                .is_some_and(|remedy| remedy.contains("disabledPlugins")),
            "the remedy must name the switch the developer actually holds: {:?}",
            report.remedy
        );

        // 4. And back to nothing when the plugin goes, switch or no switch.
        std::fs::remove_file(crate::hooks::cline_plugin::entry_path(&plugin_dir))
            .expect("remove the plugin");
        let gone = ClineBinding::detect().expect("the store root still exists");
        assert!(gone.capabilities().expressible.is_empty());
        assert_eq!(gone.liveness().armed, Some(false));
    }

    /// The hook path is the asset root's `Hooks` DIRECTORY, and it is under the
    /// asset root rather than the store root — the two are different trees, and
    /// the store root holds a lowercase `hooks/` for a lane this unit does not
    /// cover.
    #[test]
    fn hook_config_path_is_the_asset_roots_hooks_directory() {
        let _lock = crate::hooks::claude_code::CONFIG_DIR_ENV_LOCK
            .lock()
            .unwrap_or_else(|e| e.into_inner());
        let _seam_lock = crate::hooks::cline::SEAM_ENV_LOCK
            .lock()
            .unwrap_or_else(|e| e.into_inner());

        let root = tempfile::tempdir().expect("temp dir");
        let store = root.path().join("store");
        let assets = root.path().join("assets");
        let _seams = crate::hooks::cline::EnvOverride::apply([
            (
                crate::hooks::cline::STORE_DIR_ENV,
                Some(store.clone().into_os_string()),
            ),
            (
                crate::hooks::cline::DATA_DIR_ENV,
                Some(store.join("data").into_os_string()),
            ),
            (
                crate::hooks::cline::ASSETS_DIR_ENV,
                Some(assets.clone().into_os_string()),
            ),
        ]);

        let binding = ClineBinding::detached();
        assert_eq!(binding.hook_config_path(), assets.join("Hooks"));
        // And the SHAPE, not just the path. `Path::exists()` is true for a
        // directory, so the variant is the only thing that keeps this path out
        // of a file reader — the two answers must never disagree.
        assert_eq!(
            binding.hook_surface(),
            HookSurface::Directory(assets.join("Hooks")),
        );
        assert_eq!(
            binding.config_dir(),
            store,
            "config_dir is the STORE root, a different tree from the asset root"
        );
    }

    /// The plugin goes under the **store** root, not the asset root — the one
    /// place Cline's plugin search looks.
    ///
    /// Two different trees, and the pair of assertions is the point: the ten
    /// hook scripts go one way and the plugin the other, so a single test that
    /// only checked the plugin path would pass on a build that had quietly made
    /// the two roots the same.
    #[test]
    fn the_plugin_surface_is_under_the_store_root() {
        let _lock = crate::hooks::claude_code::CONFIG_DIR_ENV_LOCK
            .lock()
            .unwrap_or_else(|e| e.into_inner());
        let _seam_lock = crate::hooks::cline::SEAM_ENV_LOCK
            .lock()
            .unwrap_or_else(|e| e.into_inner());

        let root = tempfile::tempdir().expect("temp dir");
        let store = root.path().join("store");
        let assets = root.path().join("assets");
        let _seams = crate::hooks::cline::EnvOverride::apply([
            (
                crate::hooks::cline::STORE_DIR_ENV,
                Some(store.clone().into_os_string()),
            ),
            (
                crate::hooks::cline::DATA_DIR_ENV,
                Some(store.join("data").into_os_string()),
            ),
            (
                crate::hooks::cline::ASSETS_DIR_ENV,
                Some(assets.clone().into_os_string()),
            ),
        ]);

        let binding = ClineBinding::detached();
        let plugin_dir = binding.plugin_surface().expect("Cline has a plugin lane");

        assert_eq!(
            plugin_dir,
            store.join("plugins").join("openlatch"),
            "Cline's plugin search is `join(resolveClineDir(), \"plugins\")`; anywhere \
             else is a file it never loads"
        );
        assert!(
            !plugin_dir.starts_with(&assets),
            "the plugin must not land under the asset root, where the ten live"
        );
        assert_eq!(
            plugin_dir
                .file_name()
                .expect("a directory name")
                .to_string_lossy(),
            crate::hooks::cline_plugin::PLUGIN_ID,
            "the directory name IS the id `disabledPlugins` matches"
        );
    }

    /// `detect()` stats the store root: present means installed, absent means
    /// this host has no Cline. The positive half of
    /// `detect_returns_none_when_seam_path_absent`.
    #[test]
    fn detect_stats_the_store_root() {
        let _lock = crate::hooks::claude_code::CONFIG_DIR_ENV_LOCK
            .lock()
            .unwrap_or_else(|e| e.into_inner());
        let _seam_lock = crate::hooks::cline::SEAM_ENV_LOCK
            .lock()
            .unwrap_or_else(|e| e.into_inner());

        let root = tempfile::tempdir().expect("temp dir");
        let store = root.path().join("store");
        let _seams = crate::hooks::cline::EnvOverride::apply([
            (
                crate::hooks::cline::STORE_DIR_ENV,
                Some(store.clone().into_os_string()),
            ),
            (
                crate::hooks::cline::DATA_DIR_ENV,
                Some(store.join("data").into_os_string()),
            ),
            (
                crate::hooks::cline::ASSETS_DIR_ENV,
                Some(root.path().join("assets").into_os_string()),
            ),
        ]);

        assert!(
            ClineBinding::detect().is_none(),
            "a store root that does not exist is not an installed agent"
        );

        std::fs::create_dir_all(&store).expect("create the store root");
        assert!(
            ClineBinding::detect().is_some(),
            "and the directory existing is what 'installed' means"
        );
    }

    /// `build_hook_entry` must NOT panic — the adversarial review of 2026-09-14
    /// proved the "unreachable by construction" claim false.
    /// `daemon::reconciler::compute_field_deltas` (`reconciler/mod.rs:571`)
    /// reaches it without passing the reconciler's `installable()` guard, which
    /// sits in `try_heal` (`:385`), a different function. Driven by persisted
    /// state plus filesystem contents, both fallible input.
    #[test]
    fn build_hook_entry_is_inert_rather_than_a_panic() {
        let marker = OpenlatchMarker::new("test-install-id".to_string());
        let entry = ClineBinding::detached().build_hook_entry(
            "PreToolUse",
            std::path::Path::new("/nonexistent/openlatch-hook"),
            1234,
            &marker,
        );
        assert!(
            entry.is_object() && entry.as_object().is_some_and(|o| o.is_empty()),
            "an agent we never register with has no hook entry shape; the empty \
             object makes a drift diff report unhealable drift, which is TRUE, \
             instead of taking the daemon down with it"
        );
    }

    /// **The dormant hazard, now live.** An isolated instance must NOT write the
    /// machine's own Cline store.
    ///
    /// `config_is_machine_global` had no production reader while
    /// `model_relay_wiring()` answered `None` — every wiring loop skips such a
    /// binding before it consults ownership. This unit gives Cline a wiring
    /// convention and un-skips it, so the two must compose: a binding that
    /// offers a request plane AND resolves to the machine's own store is one an
    /// isolated daemon may not touch, even with `own_agent_wiring = true`.
    ///
    /// The store is the REDIRECTED home's `~/.cline`, never the developer's:
    /// `HOME` is a temp directory for the duration, and the comparison
    /// `config_is_machine_global` makes is against whatever home resolves to.
    ///
    /// **Unix only, and not for want of trying.** The arrangement under test is
    /// "the resolved store IS the default home-derived one", which
    /// `config_is_machine_global` decides by comparing against
    /// `dirs::home_dir()`. On Windows that call reads `FOLDERID_Profile` through
    /// `SHGetKnownFolderPath` and consults no environment variable, so a
    /// redirected `HOME` cannot reach it — the same fact `codex_cli.rs` states
    /// as why an env seam is the only redirection that works on all three
    /// platforms. The two ways to make this branch true on Windows are to give
    /// home resolution an env seam of its own (which is the design this crate
    /// deliberately does not have) or to point `CLINE_DIR` at the real
    /// `%USERPROFILE%\.cline` and create it — writing into the developer's own
    /// Cline store, which is the one thing these tests must never do.
    ///
    /// The guard's other side IS covered on every platform, immediately below:
    /// a relocated store, through the seam, is ownable.
    #[test]
    #[cfg(all(feature = "model-relay", unix))]
    fn an_isolated_instance_does_not_own_the_machines_own_store() {
        let _lock = crate::hooks::claude_code::CONFIG_DIR_ENV_LOCK
            .lock()
            .unwrap_or_else(|e| e.into_inner());
        let _seam_lock = crate::hooks::cline::SEAM_ENV_LOCK
            .lock()
            .unwrap_or_else(|e| e.into_inner());

        let root = tempfile::tempdir().expect("temp dir");
        let home = root.path().join("home");
        let store = home.join(".cline");
        let settings = store.join("data").join("settings");
        std::fs::create_dir_all(&settings).expect("seed the machine-global store");
        std::fs::write(
            settings.join("providers.json"),
            r#"{"lastUsedProvider":"ollama","providers":{"ollama":{"settings":{"provider":"ollama","baseUrl":"http://127.0.0.1:11434"}}}}"#,
        )
        .expect("seed providers.json");

        // CLINE_DIR deliberately UNSET, so `store_root()` falls through to the
        // redirected home's `~/.cline` — which is what "machine-global" means
        // for this process. The seam guard is not taken and must not be: its
        // whole job is to refuse this arrangement, and this is the one test
        // whose subject IS that arrangement. Nothing here writes: both calls
        // below are pure path work over a temp tree.
        let _seams = crate::hooks::cline::EnvOverride::apply([
            ("HOME", Some(home.clone().into_os_string())),
            (crate::hooks::cline::STORE_DIR_ENV, None),
            (crate::hooks::cline::DATA_DIR_ENV, None),
            (
                crate::hooks::cline::ASSETS_DIR_ENV,
                Some(root.path().join("assets").into_os_string()),
            ),
        ]);

        let binding = ClineBinding::detect().expect("the store root was just created");
        assert!(
            binding.provider_endpoints().is_some(),
            "the premise: this binding offers provider slots, which is what \
             un-skips the ownership guard"
        );
        assert!(
            binding.config_is_machine_global(),
            "and the store it resolves to is the machine's own — a `false` here \
             would read as 'relocated, safe to own'"
        );

        // An isolated instance: a non-default port, with the explicit opt-in.
        let mut cfg = crate::config::Config::defaults();
        cfg.model_relay.port = crate::model_relay::default_model_relay_port() + 1;
        cfg.model_relay.own_agent_wiring = Some(true);
        assert!(
            !crate::daemon::owns_wiring_for(&cfg, &binding),
            "an isolated daemon must not seize the machine's shared Cline store, \
             opt-in or not — opting in is a decision about your own sandbox"
        );

        // The same daemon on the default port does own it.
        cfg.model_relay.port = crate::model_relay::default_model_relay_port();
        assert!(crate::daemon::owns_wiring_for(&cfg, &binding));
    }

    /// The same guard from the other side — and this one runs on every platform.
    ///
    /// A store reached through `CLINE_DIR` is *relocated*, so an isolated daemon
    /// that opted in may own it: the rule refuses to seize the machine's shared
    /// store, it does not refuse to wire anything at all. A `false` from
    /// `config_is_machine_global` here is what the guard reads as "safe to own".
    ///
    /// This is the Windows-reachable half. It redirects through the **env seam**
    /// instead of `HOME` — the only redirection that works on all three
    /// platforms — and every path it touches is inside a temp directory, so the
    /// developer's own `~/.cline` is never read from or written to.
    #[test]
    #[cfg(feature = "model-relay")]
    fn an_isolated_instance_does_own_a_relocated_store() {
        let _lock = crate::hooks::claude_code::CONFIG_DIR_ENV_LOCK
            .lock()
            .unwrap_or_else(|e| e.into_inner());
        let _seam_lock = crate::hooks::cline::SEAM_ENV_LOCK
            .lock()
            .unwrap_or_else(|e| e.into_inner());

        let root = tempfile::tempdir().expect("temp dir");
        let store = root.path().join("relocated-cline");
        let settings = store.join("data").join("settings");
        std::fs::create_dir_all(&settings).expect("seed the relocated store");
        std::fs::write(
            settings.join("providers.json"),
            r#"{"lastUsedProvider":"ollama","providers":{"ollama":{"settings":{"provider":"ollama","baseUrl":"http://127.0.0.1:11434"}}}}"#,
        )
        .expect("seed providers.json");

        let _seams = crate::hooks::cline::EnvOverride::apply([
            (
                crate::hooks::cline::STORE_DIR_ENV,
                Some(store.clone().into_os_string()),
            ),
            (
                crate::hooks::cline::DATA_DIR_ENV,
                Some(store.join("data").into_os_string()),
            ),
            (
                crate::hooks::cline::ASSETS_DIR_ENV,
                Some(root.path().join("assets").into_os_string()),
            ),
        ]);

        let binding = ClineBinding::detect().expect("the relocated store exists");
        assert!(
            binding.provider_endpoints().is_some(),
            "the premise: provider slots are what un-skip the guard at all"
        );
        assert!(
            !binding.config_is_machine_global(),
            "a store reached through CLINE_DIR is relocated, not the machine's own"
        );

        let mut cfg = crate::config::Config::defaults();
        cfg.model_relay.port = crate::model_relay::default_model_relay_port() + 1;
        cfg.model_relay.own_agent_wiring = Some(true);
        assert!(
            crate::daemon::owns_wiring_for(&cfg, &binding),
            "opting in owns your OWN relocated store — the refusal is only ever \
             about the machine-global one"
        );
    }

    /// `config_is_machine_global` DELEGATES, exactly as the two shipped
    /// bindings do, rather than answering a hardcoded `false`.
    ///
    /// The seam guard gives us a redirected store, so the honest answer here is
    /// `false` — but it must be false BECAUSE the store is relocated, not
    /// because the method is a constant. The delegation is what this asserts;
    /// `hooks::cline`'s own tests cover the comparison itself.
    #[test]
    fn config_is_machine_global_is_delegated_not_hardcoded() {
        let _lock = crate::hooks::claude_code::CONFIG_DIR_ENV_LOCK
            .lock()
            .unwrap_or_else(|e| e.into_inner());
        let _seam_lock = crate::hooks::cline::SEAM_ENV_LOCK
            .lock()
            .unwrap_or_else(|e| e.into_inner());
        let root = tempfile::tempdir().expect("temp dir");
        let _seams = crate::hooks::cline::EnvOverride::apply(
            crate::hooks::cline::absent_seams(root.path())
                .map(|(k, v)| (k, Some(v.into_os_string()))),
        );
        assert_eq!(
            ClineBinding::detached().config_is_machine_global(),
            crate::hooks::cline::config_is_machine_global(),
            "the binding must answer with the resolver, not a literal"
        );
    }
}