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
//! Atomic `goals.json` hot-cache persistence for the SM goal store (§9.4).
//!
//! Why: §9.4 mirrors the active goal set in a daemon state file
//! (`~/.trusty-mpm/sm/goals.json`) so the TUI can render goals without a memory
//! round-trip on every poll. The palace is the source of truth; this file is a
//! DERIVED cache, rebuilt from the palace on startup and re-written on every
//! mutation. The write must be ATOMIC — a crash mid-write must never leave a
//! truncated, unparseable cache — so we write to a sibling temp file and rename it
//! over the target (atomic on the same filesystem), exactly mirroring SM-5's
//! conversation-state store. The storage ROOT is injectable so tests use a
//! `tempdir` instead of the real home directory.
//! What: [`GoalCache`] owns the `<root>/sm/` directory and the `goals.json` path;
//! [`GoalCache::save`] serialises the goal list via write-tmp-then-rename, and
//! [`GoalCache::load`] reconstructs it (a missing file loads as an empty list, so
//! first-touch is not an error). Errors map to the shared [`SmGoalError`].
//! Test: `goals/cache_tests.rs` covers round-trip identity, atomic overwrite, the
//! missing-file → empty path, and a malformed-file error.
use ;
use ;
use ;
use ;
use Goal;
/// Process-wide monotonic counter that uniquifies temp-file names per `save`.
///
/// Why: two `save` calls in the same nanosecond (coarse clock) would otherwise
/// generate identical temp paths and race; a monotonic counter mixed with the
/// wall-clock nanos guarantees a distinct temp path per call (same rationale as
/// SM-5's conversation store).
/// What: incremented once per `save`; mixed into the temp filename.
/// Test: exercised indirectly by the cache round-trip tests.
static TEMP_FILE_COUNTER: AtomicU64 = new;
/// Subdirectory under the data root that holds SM state files.
///
/// Why: §9.4 nests `goals.json` under `~/.trusty-mpm/sm/`, alongside the SM
/// conversation state files; isolating the segment keeps the layout consistent.
/// What: the `"sm"` path segment joined under the injected root.
/// Test: `cache_path_is_under_sm_subdir`.
pub const SM_STATE_SUBDIR: &str = "sm";
/// The cache filename under the `sm/` subdirectory.
///
/// Why: a single well-known name (`goals.json`, §9.4) the TUI and the store agree
/// on.
/// What: the basename joined under `<root>/sm/`.
/// Test: `cache_path_is_under_sm_subdir`.
pub const GOALS_CACHE_FILE: &str = "goals.json";
/// Atomic, root-injectable store for the `goals.json` hot cache (§9.4).
///
/// Why: centralises the path layout and the write-tmp-then-rename atomicity in one
/// tested type, and makes the storage root a constructor argument so tests never
/// write to `~/.trusty-mpm`. The daemon (SM-7) builds it from the real data dir;
/// tests build it from a `tempdir`.
/// What: holds `<root>/sm/` and persists the goal list as `goals.json`.
/// Test: `goals/cache_tests.rs`.
/// Wall-clock nanoseconds since the Unix epoch, for temp-file uniqueness.
///
/// Why: combined with the pid and the monotonic counter, the nanosecond timestamp
/// makes a temp filename overwhelmingly unlikely to collide (same approach as
/// SM-5's conversation store).
/// What: returns `SystemTime::now()` as nanos since the epoch, falling back to `0`
/// if the clock predates the epoch (the counter still guarantees per-call
/// uniqueness, so `0` is safe).
/// Test: exercised indirectly by the cache round-trip tests.