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
//! `tm session delete <id> [--force]` — hard-delete a managed session RECORD (#2012).
//!
//! Why: extracted from `commands::managed` (which sits at the 500-SLOC
//! production cap) into its own file, mirroring how `commands::prune` already
//! separates the `prune-idle` verb from the rest of the managed-session CLI
//! handlers.
//! What: [`session_delete`] POSTs the daemon's hard-delete endpoint and renders
//! the 404/409/success outcomes for the terminal.
//! Test: HTTP path covered by `delete_route_*` in tests/session_manager_mvp.rs;
//! CLI parse by `cli_parses_session_delete`.
/// `tm session delete <id> [--force]` — hard-delete a managed session RECORD (#2012).
///
/// Why: distinct from `decommission` (stop runtime + maybe remove workspace +
/// tombstone) — this permanently drops the record from the store via the
/// existing tombstone-compaction primitive, for an operator who wants a
/// mis-provisioned or stale record gone outright rather than left as a
/// `Decommissioned` tombstone forever. Fail-closed: refuses a RUNNING session
/// unless `--force` is passed, printing an actionable message telling the
/// operator to stop it first (or force it).
/// What: delegates to [`super::picker_delete::delete_managed_then_local`], which
/// POSTs `/api/v1/sessions/managed/{id}/delete?force=<bool>` and, on a managed
/// 404, falls back to the project-session `DELETE /sessions/{id}` path — guarded
/// client-side by [`super::picker_delete::local_session_needs_force`], since that
/// legacy route has no server-side running guard of its own — so the verb
/// refuses a running session whether the id names a managed or a local session
/// (#2304). A not-found in BOTH stores prints "not found"; a running-session
/// refusal (the managed daemon's 409, or the local guard's own message) prints
/// the actionable reason and returns an `Err` so scripts see a non-zero exit;
/// success prints a confirmation naming the pre-deletion name and state.
/// Test: HTTP path covered by `delete_route_*` in tests/session_manager_mvp.rs;
/// CLI parse by `cli_parses_session_delete`; the shared routing seam is
/// unit-tested via `classify_managed_delete_*`.
pub(crate) async fn session_delete(
client: &reqwest::Client,
url: &str,
id: String,
force: bool,
) -> anyhow::Result<()> {
use super::picker_delete::DeleteReport;
match super::picker_delete::delete_managed_then_local(client, url, &id, force).await? {
DeleteReport::Deleted {
name,
prior_state,
local,
} => {
if local {
// The legacy project-session store has no `--deleted--` state; a
// local delete genuinely removes the record.
println!("deleted {id} ({name}) [was {prior_state}] — removed from project store");
} else {
// Managed sessions are soft-deleted: marked `--deleted--`, still
// listed. `tm sessions prune --state deleted` drops the tombstone.
println!(
"deleted {id} ({name}) [was {prior_state}] — marked --deleted-- \
(still listed; `tm sessions prune --state deleted` to remove)"
);
}
Ok(())
}
DeleteReport::NotFound => {
println!("not found");
Ok(())
}
DeleteReport::Refused(msg) => {
eprintln!("error: {msg}");
Err(anyhow::anyhow!("delete refused: {msg}"))
}
}
}