chore(clippy): clear warning backlog and harden conversions (#383)

This commit is contained in:
Chummy 2026-02-17 00:32:33 +08:00 committed by GitHub
parent a91516df7a
commit 3234159c6c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 77 additions and 69 deletions

View file

@ -1247,18 +1247,16 @@ Done."#;
// Recovery Tests - Constants Validation
// ═══════════════════════════════════════════════════════════════════════
#[test]
fn max_tool_iterations_is_reasonable() {
// Recovery: MAX_TOOL_ITERATIONS should be set to prevent runaway loops
const _: () = {
assert!(MAX_TOOL_ITERATIONS > 0);
assert!(MAX_TOOL_ITERATIONS <= 100);
}
#[test]
fn max_history_messages_is_reasonable() {
// Recovery: MAX_HISTORY_MESSAGES should be set to prevent memory bloat
assert!(MAX_HISTORY_MESSAGES > 0);
assert!(MAX_HISTORY_MESSAGES <= 1000);
};
#[test]
fn constants_bounds_are_compile_time_checked() {
// Bounds are enforced by the const assertions above.
}
// ═══════════════════════════════════════════════════════════════════════

View file

@ -1199,7 +1199,7 @@ pub struct LarkConfig {
// ── Security Config ─────────────────────────────────────────────────
/// Security configuration for sandboxing, resource limits, and audit logging
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct SecurityConfig {
/// Sandbox configuration
#[serde(default)]
@ -1214,16 +1214,6 @@ pub struct SecurityConfig {
pub audit: AuditConfig,
}
impl Default for SecurityConfig {
fn default() -> Self {
Self {
sandbox: SandboxConfig::default(),
resources: ResourceLimitsConfig::default(),
audit: AuditConfig::default(),
}
}
}
/// Sandbox configuration for OS-level isolation
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SandboxConfig {
@ -1251,10 +1241,11 @@ impl Default for SandboxConfig {
}
/// Sandbox backend selection
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(rename_all = "lowercase")]
pub enum SandboxBackend {
/// Auto-detect best available (default)
#[default]
Auto,
/// Landlock (Linux kernel LSM, native)
Landlock,
@ -1268,12 +1259,6 @@ pub enum SandboxBackend {
None,
}
impl Default for SandboxBackend {
fn default() -> Self {
Self::Auto
}
}
/// Resource limits for command execution
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResourceLimitsConfig {

View file

@ -20,7 +20,7 @@ use std::path::{Path, PathBuf};
// ── Hardware transport enum ──────────────────────────────────────
/// Transport protocol used to communicate with physical hardware.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "lowercase")]
pub enum HardwareTransport {
/// Direct GPIO access on a Linux SBC (Raspberry Pi, Orange Pi, etc.)
@ -30,15 +30,10 @@ pub enum HardwareTransport {
/// SWD/JTAG debug probe (probe-rs) for bare-metal MCUs
Probe,
/// No hardware — software-only mode
#[default]
None,
}
impl Default for HardwareTransport {
fn default() -> Self {
Self::None
}
}
impl std::fmt::Display for HardwareTransport {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
@ -869,7 +864,9 @@ mod tests {
#[test]
fn validate_baud_rate_common_values_ok() {
for baud in [9600, 19200, 38400, 57600, 115200, 230400, 460800, 921600] {
for baud in [
9600, 19200, 38400, 57600, 115_200, 230_400, 460_800, 921_600,
] {
let cfg = HardwareConfig {
enabled: true,
transport: "serial".into(),
@ -938,7 +935,7 @@ mod tests {
enabled: true,
transport: "probe".into(),
serial_port: None,
baud_rate: 115200,
baud_rate: 115_200,
workspace_datasheets: false,
discovered_board: None,
probe_target: Some("nRF52840_xxAA".into()),

View file

@ -183,7 +183,9 @@ impl Observer for OtelObserver {
],
);
}
ObserverEvent::LlmRequest { .. } => {}
ObserverEvent::LlmRequest { .. }
| ObserverEvent::ToolCallStart { .. }
| ObserverEvent::TurnComplete => {}
ObserverEvent::LlmResponse {
provider,
model,
@ -247,7 +249,6 @@ impl Observer for OtelObserver {
// Note: tokens are recorded via record_metric(TokensUsed) to avoid
// double-counting. AgentEnd only records duration.
}
ObserverEvent::ToolCallStart { .. } => {}
ObserverEvent::ToolCall {
tool,
duration,
@ -285,7 +286,6 @@ impl Observer for OtelObserver {
self.tool_duration
.record(secs, &[KeyValue::new("tool", tool.clone())]);
}
ObserverEvent::TurnComplete => {}
ObserverEvent::ChannelMessage { channel, direction } => {
self.channel_messages.add(
1,

View file

@ -1999,7 +1999,7 @@ fn setup_hardware() -> Result<HardwareConfig> {
hw_config.baud_rate = match baud_idx {
1 => 9600,
2 => 57600,
3 => 230400,
3 => 230_400,
4 => {
let custom: String = Input::new()
.with_prompt(" Custom baud rate")

View file

@ -57,7 +57,12 @@ fn parse_retry_after_ms(err: &anyhow::Error) -> Option<u64> {
.take_while(|c| c.is_ascii_digit() || *c == '.')
.collect();
if let Ok(secs) = num_str.parse::<f64>() {
return Some((secs * 1000.0) as u64);
if secs.is_finite() && secs >= 0.0 {
let millis = Duration::from_secs_f64(secs).as_millis();
if let Ok(value) = u64::try_from(millis) {
return Some(value);
}
}
}
}
}

View file

@ -150,6 +150,18 @@ pub struct AuditLogger {
buffer: Mutex<Vec<AuditEvent>>,
}
/// Structured command execution details for audit logging.
#[derive(Debug, Clone)]
pub struct CommandExecutionLog<'a> {
pub channel: &'a str,
pub command: &'a str,
pub risk_level: &'a str,
pub approved: bool,
pub allowed: bool,
pub success: bool,
pub duration_ms: u64,
}
impl AuditLogger {
/// Create a new audit logger
pub fn new(config: AuditConfig, zeroclaw_dir: PathBuf) -> Result<Self> {
@ -183,7 +195,23 @@ impl AuditLogger {
Ok(())
}
/// Log a command execution event
/// Log a command execution event.
pub fn log_command_event(&self, entry: CommandExecutionLog<'_>) -> Result<()> {
let event = AuditEvent::new(AuditEventType::CommandExecution)
.with_actor(entry.channel.to_string(), None, None)
.with_action(
entry.command.to_string(),
entry.risk_level.to_string(),
entry.approved,
entry.allowed,
)
.with_result(entry.success, None, entry.duration_ms, None);
self.log(&event)
}
/// Backward-compatible helper to log a command execution event.
#[allow(clippy::too_many_arguments)]
pub fn log_command(
&self,
channel: &str,
@ -194,24 +222,22 @@ impl AuditLogger {
success: bool,
duration_ms: u64,
) -> Result<()> {
let event = AuditEvent::new(AuditEventType::CommandExecution)
.with_actor(channel.to_string(), None, None)
.with_action(
command.to_string(),
risk_level.to_string(),
approved,
allowed,
)
.with_result(success, None, duration_ms, None);
self.log(&event)
self.log_command_event(CommandExecutionLog {
channel,
command,
risk_level,
approved,
allowed,
success,
duration_ms,
})
}
/// Rotate log if it exceeds max size
fn rotate_if_needed(&self) -> Result<()> {
if let Ok(metadata) = std::fs::metadata(&self.log_path) {
let current_size_mb = metadata.len() / (1024 * 1024);
if current_size_mb >= self.config.max_size_mb as u64 {
if current_size_mb >= u64::from(self.config.max_size_mb) {
self.rotate()?;
}
}
@ -283,7 +309,8 @@ mod tests {
let json = serde_json::to_string(&event);
assert!(json.is_ok());
let parsed: AuditEvent = serde_json::from_str(&json.unwrap().as_str()).expect("parse");
let json = json.expect("serialize");
let parsed: AuditEvent = serde_json::from_str(json.as_str()).expect("parse");
assert!(parsed.actor.is_some());
assert!(parsed.action.is_some());
assert!(parsed.result.is_some());

View file

@ -902,8 +902,8 @@ mod tests {
let json_str = r#"{"name": "GMAIL_SEND_EMAIL_WITH_ATTACHMENT", "appName": "gmail", "description": "Send email with attachment & special chars: <>'\"\"", "enabled": true}"#;
let action: ComposioAction = serde_json::from_str(json_str).unwrap();
assert_eq!(action.name, "GMAIL_SEND_EMAIL_WITH_ATTACHMENT");
assert!(action.description.as_ref().unwrap().contains("&"));
assert!(action.description.as_ref().unwrap().contains("<"));
assert!(action.description.as_ref().unwrap().contains('&'));
assert!(action.description.as_ref().unwrap().contains('<'));
}
#[test]

View file

@ -31,7 +31,7 @@ impl GitOperationsTool {
|| arg_lower.starts_with("--upload-pack=")
|| arg_lower.starts_with("--receive-pack=")
|| arg_lower.contains("$(")
|| arg_lower.contains("`")
|| arg_lower.contains('`')
|| arg.contains('|')
|| arg.contains(';')
{
@ -90,10 +90,8 @@ impl GitOperationsTool {
branch = line.trim_start_matches("# branch.head ").to_string();
} else if let Some(rest) = line.strip_prefix("1 ") {
// Ordinary changed entry
let parts: Vec<&str> = rest.split(' ').collect();
if parts.len() >= 2 {
let path = parts.get(1).unwrap_or(&"");
let staging = parts.get(0).unwrap_or(&"");
let mut parts = rest.splitn(3, ' ');
if let (Some(staging), Some(path)) = (parts.next(), parts.next()) {
if !staging.is_empty() {
let status_char = staging.chars().next().unwrap_or(' ');
if status_char != '.' && status_char != ' ' {
@ -203,7 +201,8 @@ impl GitOperationsTool {
}
async fn git_log(&self, args: serde_json::Value) -> anyhow::Result<ToolResult> {
let limit = args.get("limit").and_then(|v| v.as_u64()).unwrap_or(10) as usize;
let limit_raw = args.get("limit").and_then(|v| v.as_u64()).unwrap_or(10);
let limit = usize::try_from(limit_raw).unwrap_or(usize::MAX).min(1000);
let limit_str = limit.to_string();
let output = self
@ -383,7 +382,9 @@ impl GitOperationsTool {
"pop" => self.run_git_command(&["stash", "pop"]).await,
"list" => self.run_git_command(&["stash", "list"]).await,
"drop" => {
let index = args.get("index").and_then(|v| v.as_u64()).unwrap_or(0) as i32;
let index_raw = args.get("index").and_then(|v| v.as_u64()).unwrap_or(0);
let index = i32::try_from(index_raw)
.map_err(|_| anyhow::anyhow!("stash index too large: {index_raw}"))?;
self.run_git_command(&["stash", "drop", &format!("stash@{{{index}}}")])
.await
}
@ -516,12 +517,7 @@ impl Tool for GitOperationsTool {
error: Some("Action blocked: read-only mode".into()),
});
}
AutonomyLevel::Supervised => {
// Allow but require tracking
}
AutonomyLevel::Full => {
// Allow freely
}
AutonomyLevel::Supervised | AutonomyLevel::Full => {}
}
}