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
//! The one door a destructive trusty-search index delete may pass through (#4743).
//!
//! Why: `search_gc` had two independent sites that formatted
//! `DELETE /indexes/{id}?delete_data=true` — the decommission-time removal and
//! the orphan sweep's delete loop. Destroying the data is opt-in (#4123), and
//! the daemon those requests reached was whichever one discovery found. Under
//! `cargo test` that is the OPERATOR'S live daemon: a test process resolves it
//! exactly like production does. Fixture workspaces derive their index id from
//! a bare `file_name()` — `full`, `sess`, `live` — so a real index sharing that
//! basename is destroyed by a test run, silently.
//!
//! Why a capability type rather than an `if` at each site: two review rounds on
//! PR #4725 each found a different destructive effect that a caller had failed
//! to gate, and the response there was the same one taken here — stop relying on
//! every call site remembering, and make the ungated shape unrepresentable. A
//! caller cannot build the request, because [`DestructiveIndexDelete`] holds the
//! only copy of the `delete_data` opt-in and exposes no constructor that takes a
//! daemon address. The only way to obtain one is
//! [`DestructiveIndexDelete::acquire`], which decides for itself whether the
//! process may destroy data. A new destructive site added later inherits the
//! refusal by construction: it has to call `acquire` to get anything it can
//! delete with.
//!
//! Why the refusal is a RUNTIME check: the delete lands in a DIFFERENT PROCESS.
//! Issue #4094's `cfg(test)` arm in trusty-search's `default_data_dir` isolates
//! that daemon's own data-dir resolution, and #4255/PR #4864 extended isolation
//! to registry writes — but neither can help here. No compile-time guard in
//! trusty-search governs what a trusty-mpm test binary puts on the wire.
//! `trusty_common::running_under_test_harness` is the process-level answer, and
//! reusing it keeps this crate from growing a second, drifting copy of the same
//! detection.
//!
//! What #6285 changed: the transport, and nothing else. The call is
//! `search.index.delete` over the daemon's Unix socket (ADR-0032) rather than
//! `DELETE /indexes/{id}`, sent through [`crate::daemon::search_rpc`] — this
//! crate's one trusty-search client. Who may acquire the capability did not
//! move: the test-harness refusal is still the first thing `acquire` evaluates,
//! still ahead of any resolution, and the `delete_data` opt-in still exists in
//! exactly one place.
//!
//! Test: `acquire_is_refused_under_a_test_harness`,
//! `acquire_refuses_when_no_daemon_socket_is_bound`,
//! `acquire_succeeds_when_production_state_is_explicitly_allowed`,
//! `delete_params_opt_into_data_deletion`,
//! `delete_over_a_stale_socket_is_a_transport_failure`,
//! `a_refusal_is_never_reported_as_removed`,
//! `a_result_that_did_not_remove_is_not_reported_as_removed`; end-to-end,
//! `decommission_issues_no_request_to_a_live_daemon_under_test` in
//! `search_gc_guard_tests`.
use PathBuf;
use Duration;
use Value;
use ;
use crate;
/// Per-request timeout for the index delete.
///
/// Why: the destructive call runs off the interactive request path
/// (decommission, periodic GC) but must still never hang the daemon
/// indefinitely if trusty-search is wedged.
const REQUEST_TIMEOUT: Duration = from_secs;
/// What a destructive index delete did, for the caller to log in its own voice.
///
/// Why: the two call sites want different log messages for the same outcomes,
/// and threading the RPC types back to them would leak the transport this
/// module exists to own.
///
/// Why four variants rather than worked / did not work: only [`Self::Removed`]
/// licenses a caller to record an index as gone. The other three are distinct
/// evidence — the daemon refused, the daemon answered but kept the index, or
/// nothing answered at all — and collapsing them lets an unanswered call read
/// as a completed one.
pub
/// The capability to destroy a trusty-search index's on-disk data (#4743).
///
/// Why: see the module doc — holding one of these is the proof that this
/// process is allowed to delete real data, and it is the only thing in the
/// crate that can produce a `delete_data` request.
/// What: the socket of a trusty-search daemon that was bound when the capability
/// was acquired. The field is private and there is no constructor other than
/// [`Self::acquire`], so the capability cannot be forged from a path a caller
/// happens to have.
/// Test: see the module doc.
pub
/// Read a delete result as the daemon's own verdict, not as "it answered".
///
/// Why: `search.index.delete` answers a RESULT — not an error — for a delete it
/// abandoned because an in-flight writer never quiesced (#3049). Over HTTP that
/// was a `200`, and treating any 2xx as success recorded an index as reclaimed
/// while it was still registered and still on disk. The daemon states the
/// outcome in the body; this reads it.
/// What: [`DeleteOutcome::Removed`] only when the body's `removed` is `true`. A
/// body that says otherwise — or that omits the field, which is unverifiable
/// rather than affirmative — is [`DeleteOutcome::NotRemoved`] carrying the
/// daemon's `quiesced` flag, the field that explains the abandonment.
/// Test: `a_result_that_did_not_remove_is_not_reported_as_removed`,
/// `a_result_reporting_removal_is_removed`.