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
//! The one way anything outside trusty-memory calls the running daemon (#6286).
//!
//! Why: this module used to resolve an address — `TRUSTY_MEMORY_URL`, else the
//! `http_addr` discovery file, else a guaranteed-dead placeholder — and POST a
//! JSON-RPC envelope to `{base}/rpc`. ADR-0032 retired that listener: since
//! #6286 trusty-memory binds one hardened Unix socket at the path
//! [`crate::daemon_socket_path`] derives, writes no discovery file, and speaks
//! the framed JSON-RPC envelope [`crate::uds`] defines. There is no address to
//! discover, no port to walk, and nothing for a stale file to disagree with.
//!
//! What: [`crate::memory_rpc::call_memory_tool`] derives the socket, writes one frame, reads one
//! back, and returns the envelope's `result`.
//! [`crate::memory_rpc::call_memory_tool_at`] takes the
//! socket explicitly, for a caller that resolved it once and threads it through
//! (catch-up's `CatchupOptions::memory_socket`) or a test pointing at a daemon
//! it started itself.
//!
//! **This module is now what the monitor TUI's client and trusty-agents'
//! `TrustyMemoryClient` call too.** Both were independent REST clients against
//! `/api/v1/*` routes, and the doc comment here used to say so and disclaim
//! reusing them. #6286 folded both onto this function: the routes they targeted
//! no longer exist, and re-deriving a second socket client for each would be
//! the drift the workspace's common-entry-point rule exists to prevent.
//!
//! **This is the request/response half only.** `memory.chat` answers in many
//! frames; a caller that wants it uses
//! [`crate::uds::send_framed_stream_request_capped`] directly, because a stream
//! is not something these signatures can return.
//!
//! Test: `call_memory_tool_at_reports_a_dead_socket_rather_than_hanging`,
//! `resolve_memory_socket_honours_the_env_override`,
//! `resolve_memory_socket_or_unreachable_falls_back`.
use ;
use Duration;
use ;
use ;
use cratesend_framed_request_capped;
use crateRpcResponse;
/// Environment variable that pins the daemon's socket path explicitly.
///
/// Why: it replaces `TRUSTY_MEMORY_URL`, which named a base URL there is no
/// longer a listener for. The affordance it provided is still wanted — a test
/// rig or a CI job points a client at a daemon it started on a temp path — and
/// the alternative, `TRUSTY_DATA_DIR_OVERRIDE`, is process-global and would
/// redirect every other trusty-* client in the same process along with this one.
///
/// What: the literal env var name `TRUSTY_MEMORY_SOCKET`, read as a path.
/// Test: `resolve_memory_socket_honours_the_env_override`.
pub const TRUSTY_MEMORY_SOCKET_ENV: &str = "TRUSTY_MEMORY_SOCKET";
/// The app name trusty-memory derives its socket path under.
///
/// Matches the daemon's own `daemon_socket_path("trusty-memory")` call, which
/// is why caller and daemon compute the same path with nothing published
/// between them.
const MEMORY_APP_NAME: &str = "trusty-memory";
/// Largest frame this client reads or writes, in bytes.
///
/// Why not [`crate::uds::MAX_FRAME_BYTES`] (8 MiB): the daemon's own budget is
/// 32 MiB (`trusty_memory::transport::uds::MAX_FRAME_BYTES`), sized for whole-
/// palace KG dumps and 500-row activity pages, and the budget is symmetric —
/// a client that kept the shared default would refuse frames the daemon
/// considers legal, which only moves which end reports the failure.
///
/// **This is a second copy of the daemon's figure, and deliberately so.**
/// `trusty-common` is below `trusty-memory` in the dependency graph, so it
/// cannot import the constant. `memory_rpc_frame_budget_matches_the_daemon` in
/// `trusty-memory/tests/uds_consumer_contract.rs` is what keeps them equal.
pub const MAX_FRAME_BYTES: u64 = 32 * 1024 * 1024;
/// Default budget for one call.
///
/// Why 5 seconds: it is the timeout the retired `reqwest` client carried, kept
/// so this migration changes the transport and not what a slow daemon looks
/// like to a caller. A caller with different needs passes its own through
/// [`call_memory_tool_at_with_timeout`] — the monitor TUI polls on a 3-second
/// tick, and a bulk import wants far longer than either.
pub const DEFAULT_TIMEOUT: Duration = from_secs;
/// The JSON-RPC code trusty-memory answers when the thing asked for is absent.
///
/// Why it is duplicated here: `trusty-common` is below `trusty-memory` in the
/// dependency graph, so `trusty_memory::transport::api_error::CODE_NOT_FOUND`
/// cannot be imported. The value is pinned by
/// `memory_rpc_not_found_code_matches_the_daemon` in
/// `trusty-memory/tests/uds_consumer_contract.rs`.
pub const CODE_NOT_FOUND: i64 = -32004;
/// The daemon answered, and what it answered was an error.
///
/// Why a typed error rather than a formatted string: a caller sometimes has to
/// tell one refusal from another. trusty-agents' memory backend reads a drawer
/// out of a palace that may never have been created, and "no such palace" has
/// to be a clean empty result while a transport failure or an internal error
/// has to propagate — the same distinction its REST predecessor drew from a 404
/// status. Carrying it in the error means [`call_memory_tool_at`] keeps its
/// `Result<Value>` signature and only the callers that care pay for it, via
/// `anyhow::Error::downcast_ref`.
///
/// Test: `call_memory_tool_at_reports_a_dead_socket_rather_than_hanging` covers
/// the transport half; the code itself is pinned against the daemon by
/// `memory_rpc_not_found_code_matches_the_daemon` in
/// `trusty-memory/tests/uds_consumer_contract.rs`, and trusty-agents'
/// `get_and_delete_are_clean_when_absent` covers the caller that reads it.
/// A path nothing can be serving, for a caller that must never see an error.
///
/// Why a path under a directory that cannot exist rather than an empty one: a
/// dial against it is refused by the kernel immediately, which is what makes
/// the fail-open callers below fail fast instead of waiting out a budget.
const UNREACHABLE_PLACEHOLDER: &str = "/nonexistent/trusty-memory/trusty-memory.sock";
/// Resolve the socket the trusty-memory daemon binds.
///
/// # Errors
///
/// When the data directory cannot be resolved or created — an operator-fixable
/// condition (permissions, a `TRUSTY_DATA_DIR_OVERRIDE` pointing somewhere
/// unusable), distinct from "the daemon is not running", which this function
/// cannot and does not report.
///
/// Test: `resolve_memory_socket_honours_the_env_override`.
/// Fail-open variant of [`resolve_memory_socket`].
///
/// Why: catch-up, identity seeding, and the TUI health poller all degrade
/// gracefully when trusty-memory is unreachable rather than aborting their
/// caller. Keeping the "give me *a* path, even a dead one" policy here means it
/// is one decision rather than an `unwrap_or_else` at every call site.
///
/// What: delegates to [`resolve_memory_socket`]; on `Err`, warns on stderr and
/// returns [`UNREACHABLE_PLACEHOLDER`].
///
/// Test: `resolve_memory_socket_or_unreachable_falls_back`.
/// Call one method on the running daemon and return its `result`.
///
/// # Errors
///
/// When the socket cannot be resolved or dialled — which is what "the daemon is
/// not running" looks like — or when the daemon answers with a JSON-RPC error,
/// whose message and code are carried through so the caller reports the reason
/// it was given rather than a generic failure.
pub async
/// [`call_memory_tool`] against an already-resolved socket.
///
/// Why: catch-up resolves once into `CatchupOptions::memory_socket` and threads
/// it through, and a test drives a daemon on a temp path. Re-resolving per call
/// would make both impossible without mutating process-global state.
///
/// # Errors
///
/// As [`call_memory_tool`].
///
/// Test: `call_memory_tool_at_reports_a_dead_socket_rather_than_hanging`.
pub async
/// [`call_memory_tool_at`] with an explicit budget.
///
/// # Errors
///
/// As [`call_memory_tool`].
pub async
/// Is anything serving the daemon's socket?
///
/// Why a bare connect rather than a `memory.health` call: the question is
/// whether the endpoint is live. A daemon that is up but degraded must not be
/// reported absent and then spawned on top of itself.
pub async