clippy: fix lints that crane's cargoClippy properly enforces (#538)

The naersk → crane swap in the parent commit flips clippy from
silently passing to actually failing on `-D warnings` (naersk's
`mode = "clippy"` mangled the `--` separator so the deny never took
effect). This commit clears the surfaced lints so the workspace
builds clean under the new enforcement — every fix is mechanical and
preserves behaviour. Tests still pass (160 across the workspace).

Auto-fixes via `cargo clippy --fix`:
- `doc_markdown` (19 sites): bare identifiers in doc comments
  wrapped in backticks
- `format_in_format_args`, `explicit_into_iter_loop`,
  `redundant_closure_for_method_calls`, `useless_conversion`, and
  a few more — mechanical rewrites of the kind cargo can apply
  safely.

Hand-fixed:
- `match_same_arms` (forge_notify::is_atx_heading): two arms returning
  `true` collapsed into a single `matches!` pattern.
- `cast_sign_loss` + `format_push_string` (mcp.rs status formatter):
  guarded `i64 → u64` through `u64::try_from(…).unwrap_or(0)` (status
  timestamps are always positive in practice; clamp the skew edge to
  0) and swapped `out.push_str(&format!(…))` for `write!` into the
  buffer with an infallible-writer `let _ =`.
- `doc_lazy_continuation` in turn.rs + manager_server.rs + sh4re/lib.rs:
  doc paragraphs that the markdown parser was treating as list-item
  continuations got either a separating blank line or a `/`-for-`+`
  word swap so the parser stops seeing a list.
- `unused_async` (manager_server::handle_request_schedule_prompt):
  function has no `.await`; dropped the `async` and its `.await` call
  site.
- `needless_pass_by_value` (scheduled_prompts::submit): take
  `&NewSchedule` instead of moving the struct in; updated two prod
  callers and eight test sites to pass references.
- `type_complexity` (approvals::mark_cancelled): hoisted the
  7-tuple SELECT row shape into a `type CancelLookupRow = (…);` alias.

Allow-with-reason for intentional patterns:
- `option_option` (6 sites across dashboard / scheduled_prompts /
  manager_server): `Option<Option<T>>` carries three-state PATCH
  semantics (missing key = leave alone, `Some(None)` = clear,
  `Some(Some(v))` = set). Collapsing to `Option<T>` loses the
  "clear" state.
- `dead_code` (rebuild_queue::QueueKind::Destroy /
  QueueSource::CrashRecover; topology::parent_of / default_seed):
  wire-shape variants + API surfaces kept for the upcoming features
  (#361 follow-ups, future `Destroy` queue routing, crash-recovery
  path). Allowed at the variant / function level with the rationale
  in `reason = "…"`.
- `too_many_lines` on three specific call-sites: a 117-line
  exhaustive-variant test (dashboard_events::kind_tag_matches_…),
  the meta-flake string template renderer
  (meta::render_flake_with_lookup), and the notification poll loop
  (forge_notify::poll_once) — splitting any of them would just hide
  the contiguous shape they exist to keep visible.

`nix flake check` formatting target is still broken on main itself
(pre-existing nixfmt drift across ~28 files unrelated to this PR);
left alone here so the scope stays "crane port + lints the port
exposed" and the operator's review doesn't have to triage drive-by
nixfmt churn.
This commit is contained in:
iris 2026-05-29 01:42:06 +02:00 committed by Mara
commit 9ed58ab96d
17 changed files with 643 additions and 463 deletions

View file

@ -76,13 +76,13 @@ pub async fn serve(port: u16, coord: Arc<Coordinator>) -> Result<()> {
.route("/op-send", post(post_op_send))
.route("/meta-update", post(post_meta_update))
.route("/api/schedules", get(api_schedules).post(post_schedule_new))
.route(
"/api/schedules/{id}",
axum::routing::patch(patch_schedule),
)
.route("/api/schedules/{id}", axum::routing::patch(patch_schedule))
.route("/api/schedules/{id}/cancel", post(post_schedule_cancel))
.route("/api/schedules/{id}/fire-now", post(post_schedule_fire_now))
.route("/api/rebuild-queue/{id}/cancel", post(post_rebuild_queue_cancel))
.route(
"/api/rebuild-queue/{id}/cancel",
post(post_rebuild_queue_cancel),
)
.route("/dashboard/stream", get(dashboard_stream))
.route("/dashboard/history", get(dashboard_history))
// Anything not matched by the dynamic routes above falls
@ -765,7 +765,14 @@ async fn dashboard_history(State(state): State<AppState>) -> Response {
let events: Vec<crate::dashboard_events::DashboardEvent> = messages
.into_iter()
.map(|m| match m {
crate::broker::MessageEvent::Sent { id, from, to, body, at, in_reply_to } => {
crate::broker::MessageEvent::Sent {
id,
from,
to,
body,
at,
in_reply_to,
} => {
let file_refs = scan_validated_paths(&body);
crate::dashboard_events::DashboardEvent::Sent {
seq: 0,
@ -778,7 +785,14 @@ async fn dashboard_history(State(state): State<AppState>) -> Response {
file_refs,
}
}
crate::broker::MessageEvent::Delivered { id, from, to, body, at, in_reply_to } => {
crate::broker::MessageEvent::Delivered {
id,
from,
to,
body,
at,
in_reply_to,
} => {
let file_refs = scan_validated_paths(&body);
crate::dashboard_events::DashboardEvent::Delivered {
seq: 0,
@ -1155,8 +1169,8 @@ fn resolve_state_path(
return Err(format!("path not in allow-list: {raw}"));
};
reject_symlinks_below(std::path::Path::new(root), &mapped)?;
let canonical = std::fs::canonicalize(&mapped)
.map_err(|e| format!("{}: {e}", mapped.display()))?;
let canonical =
std::fs::canonicalize(&mapped).map_err(|e| format!("{}: {e}", mapped.display()))?;
if !(canonical.starts_with(AGENTS_ROOT) || canonical.starts_with(SHARED_ROOT)) {
return Err(format!(
"resolved path escapes allow-list: {}",
@ -1174,8 +1188,8 @@ fn resolve_state_path(
));
}
}
let meta = std::fs::metadata(&canonical)
.map_err(|e| format!("stat {}: {e}", canonical.display()))?;
let meta =
std::fs::metadata(&canonical).map_err(|e| format!("stat {}: {e}", canonical.display()))?;
if meta.is_file() {
let mode = meta.permissions().mode();
if mode & 0o004 == 0 {
@ -1313,12 +1327,10 @@ pub(crate) async fn emit_tombstones_snapshot(coord: &Arc<Coordinator>) {
let containers = coord.containers_snapshot().await;
let transient_snapshot = coord.transient_snapshot();
let tombstones = build_tombstone_views(coord, &containers, &transient_snapshot);
coord.emit_dashboard_event(
crate::dashboard_events::DashboardEvent::TombstonesChanged {
seq: coord.next_seq(),
tombstones,
},
);
coord.emit_dashboard_event(crate::dashboard_events::DashboardEvent::TombstonesChanged {
seq: coord.next_seq(),
tombstones,
});
}
/// Snapshot meta/flake.lock's root inputs + emit
@ -1326,12 +1338,10 @@ pub(crate) async fn emit_tombstones_snapshot(coord: &Arc<Coordinator>) {
/// (`run_meta_update`, `auto_update::rebuild_agent`).
pub(crate) fn emit_meta_inputs_snapshot(coord: &Coordinator) {
let inputs = read_meta_inputs();
coord.emit_dashboard_event(
crate::dashboard_events::DashboardEvent::MetaInputsChanged {
seq: coord.next_seq(),
inputs,
},
);
coord.emit_dashboard_event(crate::dashboard_events::DashboardEvent::MetaInputsChanged {
seq: coord.next_seq(),
inputs,
});
}
/// Scan `body` for path-shaped tokens, validate each against the
@ -1381,9 +1391,7 @@ pub(crate) fn scan_validated_paths(body: &str) -> Vec<String> {
out
}
async fn get_state_file(
axum::extract::Query(q): axum::extract::Query<StateFileQuery>,
) -> Response {
async fn get_state_file(axum::extract::Query(q): axum::extract::Query<StateFileQuery>) -> Response {
const MAX_BYTES: usize = 1 << 20; // 1 MiB
let (canonical, meta) = match resolve_state_path(&q.path) {
Ok(pair) => pair,
@ -1415,11 +1423,18 @@ async fn get_state_file(
return ([("content-type", ct)], bytes).into_response();
}
let truncated = bytes.len() > MAX_BYTES;
let body_bytes = if truncated { &bytes[..MAX_BYTES] } else { &bytes[..] };
let body_bytes = if truncated {
&bytes[..MAX_BYTES]
} else {
&bytes[..]
};
let mut body = String::from_utf8_lossy(body_bytes).into_owned();
if truncated {
use std::fmt::Write as _;
let _ = write!(body, "\n\n--- truncated at {MAX_BYTES} of {size} bytes ---\n");
let _ = write!(
body,
"\n\n--- truncated at {MAX_BYTES} of {size} bytes ---\n"
);
}
([("content-type", "text/plain; charset=utf-8")], body).into_response()
}
@ -1492,7 +1507,7 @@ async fn post_schedule_new(
description: payload.description,
source: crate::scheduled_prompts::ScheduleSource::Operator,
};
match state.coord.scheduled_prompts.submit(new) {
match state.coord.scheduled_prompts.submit(&new) {
Ok(id) => axum::Json(serde_json::json!({"id": id})).into_response(),
Err(e) => error_response(&format!("schedule submit: {e:#}")),
}
@ -1546,6 +1561,12 @@ struct CancelScheduleForm {
}
#[derive(serde::Deserialize, Default)]
#[allow(
clippy::option_option,
reason = "double-Option carries three-state PATCH semantics on the wire \
(missing key = leave alone, JSON null = clear, value = set); \
collapsing to a single Option would lose the 'clear' state"
)]
struct EditScheduleForm {
#[serde(default)]
body: Option<String>,
@ -1671,7 +1692,10 @@ async fn get_agent_links(AxumPath(name): AxumPath<String>) -> Response {
match client.get(&url).send().await {
Ok(resp) if resp.status().is_success() => match resp.json::<serde_json::Value>().await {
Ok(body) => {
let links = body.get("links").cloned().unwrap_or_else(|| serde_json::json!([]));
let links = body
.get("links")
.cloned()
.unwrap_or_else(|| serde_json::json!([]));
axum::Json(links).into_response()
}
Err(e) => {
@ -1852,7 +1876,10 @@ async fn post_op_send(State(state): State<AppState>, Form(form): Form<OpSendForm
.coord
.broadcast_send(hive_sh4re::OPERATOR_RECIPIENT, &body);
if !errors.is_empty() {
return error_response(&format!("op-send broadcast partial fail: {}", errors.join("; ")));
return error_response(&format!(
"op-send broadcast partial fail: {}",
errors.join("; ")
));
}
} else if let Err(e) = state.coord.broker.send(&hive_sh4re::Message {
from: hive_sh4re::OPERATOR_RECIPIENT.to_owned(),
@ -2151,7 +2178,6 @@ fn gc_orphans(coord: &Coordinator, approvals: Vec<Approval>) -> Vec<Approval> {
.collect()
}
/// Multi-file unified diff between the currently-deployed tree and
/// the proposal for this approval. Runs against the applied repo
/// since the canonical proposal commit lives there (manager-side
@ -2282,4 +2308,3 @@ async fn get_approval_diff(
fn plain_text(body: String) -> Response {
(StatusCode::OK, body).into_response()
}