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
//! Safety Lock: an implicitly discovered dotfiles root must be approved
//! before it can drive a root-sensitive mutation.
//!
//! Dodot infers a dotfiles root from the enclosing Git repository, then from
//! the current directory. That convenience is also a hazard: an unrelated
//! repository looks enough like a dotfiles repository to deploy from, and a
//! wrong-root `up` can add shell files to every future session while a
//! wrong-root `down` can remove legitimate state. Safety Lock makes root
//! intent explicit without removing the convenience — read-only commands and
//! dry runs stay available on any root, and a valid `DOTFILES_ROOT` remains
//! the deliberate, noninteractive selection path.
//!
//! Governing documents: `docs/spec/safety-lock.md`,
//! `docs/adr/0001-trust-dotfiles-roots-by-canonical-path.md`,
//! `docs/adr/0002-guard-root-derived-mutations.md`, and
//! `docs/adr/0003-inspect-and-revoke-roots-without-a-trust-command.md`.
//!
//! # The shape
//!
//! ```text
//! DOTFILES_ROOT ──► environment ─┐
//! ├─► selection ─► ResolvedRoot ─► check ─► TrustDecision
//! git top-level / cwd ──► files ─┘ │ │
//! │ operation ─► authorize ─► GateOutcome
//! schema (approved roots, one file) │
//! │ ├─ NotRootSensitive
//! list / forget ├─ Permitted
//! └─ ConfirmationRequired
//! └─ inventory (prompt)
//! ```
//!
//! [`resolve_root`] is the invocation's one act of selection: `DOTFILES_ROOT`,
//! then the Git top-level, then the current directory — and nothing after
//! that. Every consumer carries the [`ResolvedRoot`] it returns rather than
//! resolving again, which is what keeps the root the user approved and the
//! root Dodot mutates the same one (ADR-0001).
//!
//! Both selection paths return the same [`ResolvedRoot`]: one canonical
//! [`RootIdentity`] plus the [`RootSource`] that chose it. Provenance changes
//! authorization policy — an environment root is already deliberate, an
//! implicit one needs approval — never the path's representation. No
//! source-specific type exists past [`roots`]; checking, listing, inventory,
//! and mutation scoping all take a `ResolvedRoot` or a `RootIdentity`.
//!
//! Approved implicit roots live together in one clapfig-managed file under
//! Dodot's data directory ([`schema`]), never in the dotfiles repository.
//! Environment roots are never written there.
//!
//! [`authorize`] is the seam a command crosses. [`decide`] answers only what
//! the trust collection can answer — what standing a root has — and a root's
//! standing only matters once you know what the command is about to do:
//! `status` and `up --dry-run` read the same untrusted root that `up` may not
//! write to. [`RootOperation`] carries that half, declared per command rather
//! than inferred from which context builder it used (ADR-0002), and the two
//! compose into one [`GateOutcome`]. An unapproved implicit root gets the
//! orientation [`inventory`] built and carried out with the answer, so no
//! approvable outcome can exist for a root Dodot could not describe.
//!
//! # Boundaries
//!
//! This module is CLI-free by construction: no Clap and no Standout types
//! appear in any signature, and nothing here reads the process environment,
//! the current directory, or Git. Those are captured at the process boundary
//! and injected ([`RootSelectionInput`], [`PathProbe`],
//! [`HostFacts`](crate::gates::HostFacts), [`Fs`](crate::fs::Fs)). Likewise,
//! persistence is the caller's: the checking, listing, and forgetting APIs
//! take an already-loaded [`SafetyLockConfig`] and return typed state changes
//! to write.
//!
//! Reading the *root* is different from reading process state, and the
//! inventory does it: pack discovery and rule matching need the directory. It
//! stops at routing metadata — configuration and which handler claims which
//! entry — and never renders, resolves, or reads a candidate file's contents
//! (Spec, "Non-Goals"). The injected [`Fs`](crate::fs::Fs) is not a full
//! virtualization of that read: configuration goes through
//! [`ConfigManager`](crate::config::ConfigManager), which uses `std::fs`. See
//! [`build_inventory`](inventory::build_inventory).
//!
//! # Where the boundary is
//!
//! The capture this module refuses to perform happens in exactly one place:
//! the CLI's `safety` module, whose Standout pre-dispatch hook reads
//! `DOTFILES_ROOT`, the current directory, and the Git top-level, calls
//! [`resolve_root`] and [`authorize`], and hands the result to the command as
//! an immutable value. Handlers consume that value; nothing downstream reads
//! process state again.
//!
//! The routes Standout never dispatches cross the same boundary through
//! their own doors: each declares its policy in the CLI's
//! `PASSTHROUGH_POLICY` table, `config`'s root-persisting actions run the
//! full capture-resolve-authorize sequence via the CLI's `gate_passthrough`,
//! and the tutorial's real deployment step runs `gate_captured` on the facts
//! and root captured when its environment was built — so the root it
//! authorizes is the root it has been showing all along.
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;