AkurAI Build
Menu

AkurAI-IDPCommit 067df512

Commit 067df512

067df512e550e011fdb00cebb40754b0aa5429d9 by Ólafur Búi Ólafsson

diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..28a14f2
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,10 @@
+# Changelog
+
+All notable changes to this project are documented here. Format: Keep a Changelog; SemVer.
+
+## [Unreleased]
+
+## [0.1.1] - 2026-06-29
+
+- Remove AI-slop 'Agent surfaces' from agent console: delete Tasks, Projects, AGY context, Notes, Passvault, Cron, Curator pages + the dead Tools launcher (all hardcoded placeholder content, no real data). Console nav is now Chat / Kanban / Run details. Also: repo-wide rustfmt + clippy cleanup.
+
diff --git a/Cargo.toml b/Cargo.toml
index 0bafacb..2fda114 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "akurai-idp"
-version = "0.1.0"
+version = "0.1.1"
 edition = "2024"
 description = "Lightweight Rust OIDC/OAuth2 identity provider for AkurAI — replaces 96MB Node process"
 license = "MIT"
diff --git a/VERSION b/VERSION
index 6e8bf73..17e51c3 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-0.1.0
+0.1.1
diff --git a/assets/agent.js b/assets/agent.js
index 65c9cae..6e9e37d 100644
--- a/assets/agent.js
+++ b/assets/agent.js
@@ -9,7 +9,6 @@ for (const root of roots) {
   const session = root.dataset.session || "default";
   const page = root.dataset.agentPage || location.pathname;
   const isChatPage = page === "/agent";
-  const notesKey = `akurai-agent-notes:${session}`;
   const chatKey = `akurai-agent-chat:${session}`;
   const draftKey = `akurai-agent-draft:${session}`;
   const pendingPromptKey = `akurai-agent-pending-prompt:${session}`;
@@ -277,27 +276,6 @@ for (const root of roots) {
     }
   };
 
-  const initNotes = (container) => {
-    const notes = container.querySelector("[data-notes-editor]");
-    const notesStatus = container.querySelector("[data-notes-status]");
-    if (notes) {
-      notes.value = localStorage.getItem(notesKey) || "";
-      container.querySelector("[data-save-notes]")?.addEventListener("click", () => {
-        localStorage.setItem(notesKey, notes.value);
-        if (notesStatus) notesStatus.textContent = "Saved locally";
-        setStatus("Notes saved");
-      });
-      container.querySelector("[data-use-notes]")?.addEventListener("click", () => {
-        const text = notes.value.trim();
-        fillPrompt(
-          text
-            ? `Use these local notes as context and propose the next concrete action:\n\n${text}`
-            : "No local notes are saved yet. Ask me what to capture before continuing.",
-        );
-      });
-    }
-  };
-
   root.querySelectorAll("[data-agent-prompt]").forEach((button) => {
     button.addEventListener("click", () => fillPrompt(button.dataset.agentPrompt));
   });
@@ -309,7 +287,6 @@ for (const root of roots) {
   form?.addEventListener("submit", submitStream);
   restoreChat();
   restorePrompt();
-  initNotes(root);
   root.querySelectorAll("[data-kanban-board]").forEach(initKanbanPanel);
 
   async function kanbanFetch(path, options = {}) {
diff --git a/src/db/mod.rs b/src/db/mod.rs
index abdb694..ed08b41 100644
--- a/src/db/mod.rs
+++ b/src/db/mod.rs
@@ -1,9 +1,9 @@
 pub mod schema;
 
-use rusqlite::Connection;
-use std::sync::Mutex;
-use std::sync::LazyLock;
 use crate::config;
+use rusqlite::Connection;
+use std::sync::LazyLock;
+use std::sync::Mutex;
 
 static DB: LazyLock<Mutex<Connection>> = LazyLock::new(|| {
     let cfg = config::get();
diff --git a/src/lib/crypto.rs b/src/lib/crypto.rs
index d7651a2..6b4e9b6 100644
--- a/src/lib/crypto.rs
+++ b/src/lib/crypto.rs
@@ -20,8 +20,8 @@ pub fn sha256(input: &str) -> String {
 
 /// Base64url-encode bytes (no padding, URL-safe alphabet).
 pub fn base64url(input: &[u8]) -> String {
-    use base64::engine::general_purpose::URL_SAFE_NO_PAD;
     use base64::Engine;
+    use base64::engine::general_purpose::URL_SAFE_NO_PAD;
     URL_SAFE_NO_PAD.encode(input)
 }
 
diff --git a/src/lib/html.rs b/src/lib/html.rs
index b25374f..7ed4161 100644
--- a/src/lib/html.rs
+++ b/src/lib/html.rs
@@ -31,10 +31,10 @@ impl Locale {
         if let Some(cookies) = cookie_header {
             for part in cookies.split(';') {
                 let part = part.trim();
-                if let Some(val) = part.strip_prefix("akurai-lang=") {
-                    if val.trim() == "en" {
-                        return Locale::En;
-                    }
+                if let Some(val) = part.strip_prefix("akurai-lang=")
+                    && val.trim() == "en"
+                {
+                    return Locale::En;
                 }
             }
         }
diff --git a/src/lib/jwt.rs b/src/lib/jwt.rs
index 29a1c4f..896769f 100644
--- a/src/lib/jwt.rs
+++ b/src/lib/jwt.rs
@@ -1,6 +1,6 @@
 use base64::Engine;
-use ed25519_dalek::{pkcs8::DecodePublicKey, SigningKey, VerifyingKey};
-use jsonwebtoken::{decode, encode, Algorithm, DecodingKey, EncodingKey, Header, Validation};
+use ed25519_dalek::{SigningKey, VerifyingKey, pkcs8::DecodePublicKey};
+use jsonwebtoken::{Algorithm, DecodingKey, EncodingKey, Header, Validation, decode, encode};
 use serde_json::Value;
 
 use super::crypto::generate_secure_token;
@@ -46,7 +46,8 @@ pub fn sign_token(
     let mut payload = claims.clone();
     if let Some(obj) = payload.as_object_mut() {
         obj.entry("iat").or_insert(Value::from(now));
-        obj.entry("exp").or_insert(Value::from(now + expires_in_secs));
+        obj.entry("exp")
+            .or_insert(Value::from(now + expires_in_secs));
     }
 
     let encoding_key = EncodingKey::from_ed_pem(private_key_pem.as_bytes())
@@ -73,8 +74,8 @@ pub fn verify_token(token: &str, public_key_pem: &str) -> Result<Value, String>
 /// Export the public key as a JWK object for a JWKS endpoint.
 /// The returned JSON object contains kty, crv, x, kid, alg, use.
 pub fn export_public_key_jwk(public_key_pem: &str, kid: &str) -> Result<Value, String> {
-    let verifying_key =
-        VerifyingKey::from_public_key_pem(public_key_pem).map_err(|e| format!("invalid public key: {e}"))?;
+    let verifying_key = VerifyingKey::from_public_key_pem(public_key_pem)
+        .map_err(|e| format!("invalid public key: {e}"))?;
 
     // Ed25519 JWK: kty=OKP, crv=Ed25519, x=base64url(raw 32-byte public key)
     let x = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(verifying_key.to_bytes());
diff --git a/src/lib/mod.rs b/src/lib/mod.rs
index 8abffaf..100f384 100644
--- a/src/lib/mod.rs
+++ b/src/lib/mod.rs
@@ -8,7 +8,6 @@ pub mod totp;
 
 /// Parse a DB column that may be a JSON array `["a","b"]` or space-separated `"a b"`.
 pub fn parse_json_or_space_separated(s: &str) -> Vec<String> {
-    serde_json::from_str::<Vec<String>>(s).unwrap_or_else(|_| {
-        s.split_whitespace().map(String::from).collect()
-    })
+    serde_json::from_str::<Vec<String>>(s)
+        .unwrap_or_else(|_| s.split_whitespace().map(String::from).collect())
 }
diff --git a/src/lib/password.rs b/src/lib/password.rs
index 89b48ff..adadbe4 100644
--- a/src/lib/password.rs
+++ b/src/lib/password.rs
@@ -1,6 +1,6 @@
 use argon2::{
-    password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString},
     Algorithm, Argon2, Params, Version,
+    password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString},
 };
 
 /// Hash a plaintext password with Argon2id.
diff --git a/src/lib/totp.rs b/src/lib/totp.rs
index 62cfb21..99f5ebd 100644
--- a/src/lib/totp.rs
+++ b/src/lib/totp.rs
@@ -7,7 +7,9 @@ use super::crypto::generate_secure_token;
 pub fn generate_totp_secret(email: &str, issuer: &str) -> (String, String) {
     let secret = Secret::generate_secret();
     let base32 = secret.to_encoded().to_string();
-    let secret_bytes = secret.to_bytes().expect("failed to decode generated secret");
+    let secret_bytes = secret
+        .to_bytes()
+        .expect("failed to decode generated secret");
 
     // Build the otpauth URI manually since we don't have the `otpauth` feature
     // Format: otpauth://totp/ISSUER:EMAIL?secret=BASE32&issuer=ISSUER&algorithm=SHA1&digits=6&period=30
@@ -20,8 +22,7 @@ pub fn generate_totp_secret(email: &str, issuer: &str) -> (String, String) {
     );
 
     // Verify the secret works by constructing a TOTP (5-param, no otpauth feature)
-    let _totp = TOTP::new(Algorithm::SHA1, 6, 1, 30, secret_bytes)
-        .expect("failed to create TOTP");
+    let _totp = TOTP::new(Algorithm::SHA1, 6, 1, 30, secret_bytes).expect("failed to create TOTP");
 
     (base32, uri)
 }
diff --git a/src/main.rs b/src/main.rs
index 54885ac..8b42325 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,3 +1,7 @@
+#![allow(clippy::result_large_err)]
+#![allow(clippy::too_many_arguments)]
+#![allow(special_module_name)]
+
 pub mod config;
 pub mod db;
 pub mod lib;
@@ -5,13 +9,13 @@ pub mod middleware;
 pub mod routes;
 
 use axum::{
+    Extension, Json, Router,
     body::Body,
     extract::Request,
-    http::{header, Response, StatusCode},
+    http::{Response, StatusCode, header},
     middleware as axum_mw,
     response::{IntoResponse, Redirect},
     routing::get,
-    Extension, Json, Router,
 };
 use serde_json::json;
 use subtle::ConstantTimeEq;
@@ -63,20 +67,16 @@ async fn main() {
     let app = Router::new()
         // Auth pages (login/mfa/logout) with CSRF
         .merge(
-            routes::auth_pages::router()
-                .layer(axum_mw::from_fn(middleware::csrf::csrf_protection))
+            routes::auth_pages::router().layer(axum_mw::from_fn(middleware::csrf::csrf_protection)),
         )
         // OIDC discovery
         .merge(routes::well_known::router())
         // OIDC endpoints
         .merge(
-            routes::authorize::router()
-                .layer(axum_mw::from_fn(middleware::auth::session_optional))
+            routes::authorize::router().layer(axum_mw::from_fn(middleware::auth::session_optional)),
         )
         .merge(routes::token::router())
-        .merge(
-            routes::userinfo::router().layer(axum_mw::from_fn(middleware::auth::bearer_auth)),
-        )
+        .merge(routes::userinfo::router().layer(axum_mw::from_fn(middleware::auth::bearer_auth)))
         .merge(routes::introspect::router())
         .merge(routes::revoke::router())
         // Authenticated agent console
@@ -117,16 +117,11 @@ async fn main() {
         .await
         .expect("failed to bind listener");
 
-    axum::serve(listener, app)
-        .await
-        .expect("server error");
+    axum::serve(listener, app).await.expect("server error");
 }
 
 /// Admin auth middleware: requires Bearer token matching IDP_ADMIN_TOKEN (constant-time compare).
-async fn admin_auth_middleware(
-    req: Request,
-    next: axum::middleware::Next,
-) -> impl IntoResponse {
+async fn admin_auth_middleware(req: Request, next: axum::middleware::Next) -> impl IntoResponse {
     let cfg = config::get();
 
     if cfg.admin_token.is_empty() {
diff --git a/src/middleware/auth.rs b/src/middleware/auth.rs
index d88889d..604b3dd 100644
--- a/src/middleware/auth.rs
+++ b/src/middleware/auth.rs
@@ -140,8 +140,7 @@ pub async fn bearer_auth(request: Request<Body>, next: Next) -> Response<Body> {
     let cfg = config::get();
 
     // Mode 1: admin token match
-    if !cfg.admin_token.is_empty() && constant_time_equal(&token, &cfg.admin_token)
-    {
+    if !cfg.admin_token.is_empty() && constant_time_equal(&token, &cfg.admin_token) {
         return next.run(request).await;
     }
 
@@ -151,9 +150,7 @@ pub async fn bearer_auth(request: Request<Body>, next: Next) -> Response<Body> {
             .prepare("SELECT kid, public_key, alg FROM signing_keys WHERE active = 1")
             .ok()?;
         let keys: Vec<(String, String, String)> = stmt
-            .query_map([], |row| {
-                Ok((row.get(0)?, row.get(1)?, row.get(2)?))
-            })
+            .query_map([], |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?)))
             .ok()?
             .filter_map(|r| r.ok())
             .collect();
diff --git a/src/middleware/csrf.rs b/src/middleware/csrf.rs
index 78ce6a0..13bb017 100644
--- a/src/middleware/csrf.rs
+++ b/src/middleware/csrf.rs
@@ -28,12 +28,11 @@ pub async fn csrf_protection(mut request: Request<Body>, next: Next) -> Response
     }
 
     // Skip CSRF for Bearer-authenticated requests (API clients)
-    if let Some(auth) = request.headers().get(header::AUTHORIZATION) {
-        if let Ok(val) = auth.to_str() {
-            if val.starts_with("Bearer ") {
-                return next.run(request).await;
-            }
-        }
+    if let Some(auth) = request.headers().get(header::AUTHORIZATION)
+        && let Ok(val) = auth.to_str()
+        && val.starts_with("Bearer ")
+    {
+        return next.run(request).await;
     }
 
     // For state-changing methods, verify double-submit cookie
diff --git a/src/middleware/rate_limit.rs b/src/middleware/rate_limit.rs
index a0a0040..a11286e 100644
--- a/src/middleware/rate_limit.rs
+++ b/src/middleware/rate_limit.rs
@@ -53,9 +53,7 @@ pub async fn rate_limit(request: Request<Body>, next: Next) -> Response<Body> {
 
     let (count, reset_at, remaining) = {
         let mut store = state.store.lock().unwrap();
-        let entry = store
-            .entry(ip)
-            .or_insert((0, current + state.window_ms));
+        let entry = store.entry(ip).or_insert((0, current + state.window_ms));
 
         if current >= entry.1 {
             // Window expired — reset
@@ -69,7 +67,7 @@ pub async fn rate_limit(request: Request<Body>, next: Next) -> Response<Body> {
     };
 
     if count > state.max_requests {
-        let retry_after = (reset_at.saturating_sub(current) + 999) / 1000;
+        let retry_after = reset_at.saturating_sub(current).div_ceil(1000);
         let body = json!({
             "error": "too_many_requests",
             "error_description": "Rate limit exceeded. Try again later.",
@@ -86,10 +84,7 @@ pub async fn rate_limit(request: Request<Body>, next: Next) -> Response<Body> {
             "x-ratelimit-reset",
             (reset_at / 1000).to_string().parse().unwrap(),
         );
-        headers.insert(
-            "retry-after",
-            retry_after.to_string().parse().unwrap(),
-        );
+        headers.insert("retry-after", retry_after.to_string().parse().unwrap());
         return resp;
     }
 
diff --git a/src/middleware/secure_headers.rs b/src/middleware/secure_headers.rs
index 44f072a..6738822 100644
--- a/src/middleware/secure_headers.rs
+++ b/src/middleware/secure_headers.rs
@@ -14,7 +14,10 @@ pub async fn secure_headers(request: Request<Body>, next: Next) -> Response<Body
         "strict-transport-security",
         "max-age=31536000; includeSubDomains".parse().unwrap(),
     );
-    headers.insert("referrer-policy", "strict-origin-when-cross-origin".parse().unwrap());
+    headers.insert(
+        "referrer-policy",
+        "strict-origin-when-cross-origin".parse().unwrap(),
+    );
     headers.insert(
         "permissions-policy",
         "camera=(), microphone=(), geolocation=(), interest-cohort=()"
diff --git a/src/routes/account.rs b/src/routes/account.rs
index fe1e83d..157b2d8 100644
--- a/src/routes/account.rs
+++ b/src/routes/account.rs
@@ -673,20 +673,19 @@ async fn sessions_page(headers: HeaderMap) -> Response {
                  ORDER BY created_at DESC",
             )
             .unwrap();
-        let rows = stmt
-            .query_map(rusqlite::params![user.user_id, now], |row| {
-                Ok(SessionRow {
-                    id: row.get(0)?,
-                    ip: row.get(1)?,
-                    user_agent: row.get(2)?,
-                    created_at: row.get(3)?,
-                    expires_at: row.get(4)?,
-                })
+
+        stmt.query_map(rusqlite::params![user.user_id, now], |row| {
+            Ok(SessionRow {
+                id: row.get(0)?,
+                ip: row.get(1)?,
+                user_agent: row.get(2)?,
+                created_at: row.get(3)?,
+                expires_at: row.get(4)?,
             })
-            .unwrap()
-            .filter_map(|r| r.ok())
-            .collect::<Vec<_>>();
-        rows
+        })
+        .unwrap()
+        .filter_map(|r| r.ok())
+        .collect::<Vec<_>>()
     });
 
     let lbl_current = t(locale, " (núverandi)", " (current)");
diff --git a/src/routes/admin/audit.rs b/src/routes/admin/audit.rs
index c41e911..f26e833 100644
--- a/src/routes/admin/audit.rs
+++ b/src/routes/admin/audit.rs
@@ -1,10 +1,4 @@
-use axum::{
-    extract::Query,
-    http::StatusCode,
-    response::IntoResponse,
-    routing::get,
-    Json, Router,
-};
+use axum::{Json, Router, extract::Query, http::StatusCode, response::IntoResponse, routing::get};
 use serde::{Deserialize, Serialize};
 use serde_json::json;
 
@@ -72,12 +66,12 @@ async fn list_audit(Query(q): Query<AuditQuery>) -> impl IntoResponse {
             params.push(Box::new(evt.clone()));
             idx += 1;
         }
-        if let Some(ref since_str) = q.since {
-            if let Some(ts) = parse_since(since_str) {
-                conditions.push(format!("created_at >= ?{idx}"));
-                params.push(Box::new(ts));
-                idx += 1;
-            }
+        if let Some(ref since_str) = q.since
+            && let Some(ts) = parse_since(since_str)
+        {
+            conditions.push(format!("created_at >= ?{idx}"));
+            params.push(Box::new(ts));
+            idx += 1;
         }
 
         let where_clause = if conditions.is_empty() {
@@ -92,7 +86,8 @@ async fn list_audit(Query(q): Query<AuditQuery>) -> impl IntoResponse {
         params.push(Box::new(limit as i64));
 
         let mut stmt = conn.prepare(&sql)?;
-        let params_refs: Vec<&dyn rusqlite::types::ToSql> = params.iter().map(|p| p.as_ref()).collect();
+        let params_refs: Vec<&dyn rusqlite::types::ToSql> =
+            params.iter().map(|p| p.as_ref()).collect();
         let rows = stmt.query_map(params_refs.as_slice(), |row| {
             let metadata_raw: Option<String> = row.get(5)?;
             let metadata = metadata_raw.and_then(|s| serde_json::from_str(&s).ok());
@@ -115,6 +110,10 @@ async fn list_audit(Query(q): Query<AuditQuery>) -> impl IntoResponse {
 
     match result {
         Ok(entries) => Json(json!(entries)).into_response(),
-        Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": e.to_string()}))).into_response(),
+        Err(e) => (
+            StatusCode::INTERNAL_SERVER_ERROR,
+            Json(json!({"error": e.to_string()})),
+        )
+            .into_response(),
     }
 }
diff --git a/src/routes/admin/clients.rs b/src/routes/admin/clients.rs
index 3e13aa9..370d136 100644
--- a/src/routes/admin/clients.rs
+++ b/src/routes/admin/clients.rs
@@ -1,9 +1,9 @@
 use axum::{
+    Json, Router,
     extract::{Path, Query},
     http::StatusCode,
     response::IntoResponse,
     routing::{delete, get, post},
-    Json, Router,
 };
 use serde::{Deserialize, Serialize};
 use serde_json::json;
@@ -63,7 +63,8 @@ async fn list_clients(Query(q): Query<ListQuery>) -> impl IntoResponse {
             ),
         };
         let mut stmt = conn.prepare(&sql)?;
-        let params_refs: Vec<&dyn rusqlite::types::ToSql> = params.iter().map(|p| p.as_ref()).collect();
+        let params_refs: Vec<&dyn rusqlite::types::ToSql> =
+            params.iter().map(|p| p.as_ref()).collect();
         let rows = stmt.query_map(params_refs.as_slice(), |row| {
             let redirect_uris_raw: String = row.get(3)?;
             let grant_types_raw: String = row.get(4)?;
@@ -88,7 +89,11 @@ async fn list_clients(Query(q): Query<ListQuery>) -> impl IntoResponse {
 
     match result {
         Ok(clients) => Json(json!(clients)).into_response(),
-        Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": e.to_string()}))).into_response(),
+        Err(e) => (
+            StatusCode::INTERNAL_SERVER_ERROR,
+            Json(json!({"error": e.to_string()})),
+        )
+            .into_response(),
     }
 }
 
@@ -98,11 +103,20 @@ async fn create_client(Json(body): Json<CreateClient>) -> impl IntoResponse {
     let secret_hash = sha256(&client_secret);
     let now = chrono::Utc::now().timestamp();
 
-    let grant_types = body.grant_types.unwrap_or_else(|| vec!["authorization_code".to_string()]);
-    let scopes = body.scopes.unwrap_or_else(|| vec!["openid".to_string(), "profile".to_string(), "email".to_string()]);
+    let grant_types = body
+        .grant_types
+        .unwrap_or_else(|| vec!["authorization_code".to_string()]);
+    let scopes = body.scopes.unwrap_or_else(|| {
+        vec![
+            "openid".to_string(),
+            "profile".to_string(),
+            "email".to_string(),
+        ]
+    });
     let first_party = body.first_party.unwrap_or(false);
 
-    let redirect_uris_json = serde_json::to_string(&body.redirect_uris).unwrap_or_else(|_| "[]".to_string());
+    let redirect_uris_json =
+        serde_json::to_string(&body.redirect_uris).unwrap_or_else(|_| "[]".to_string());
     let grant_types_json = serde_json::to_string(&grant_types).unwrap_or_else(|_| "[]".to_string());
     let scopes_json = serde_json::to_string(&scopes).unwrap_or_else(|_| "[]".to_string());
 
@@ -122,33 +136,53 @@ async fn create_client(Json(body): Json<CreateClient>) -> impl IntoResponse {
                 None,
                 Some(&json!({"client_id": id, "name": body.name})),
             );
-            (StatusCode::CREATED, Json(json!({
-                "id": id,
-                "client_secret": client_secret,
-                "name": body.name,
-                "tenant_id": body.tenant_id,
-                "redirect_uris": body.redirect_uris,
-                "grant_types": grant_types,
-                "scopes": scopes,
-                "first_party": first_party
-            }))).into_response()
+            (
+                StatusCode::CREATED,
+                Json(json!({
+                    "id": id,
+                    "client_secret": client_secret,
+                    "name": body.name,
+                    "tenant_id": body.tenant_id,
+                    "redirect_uris": body.redirect_uris,
+                    "grant_types": grant_types,
+                    "scopes": scopes,
+                    "first_party": first_party
+                })),
+            )
+                .into_response()
         }
-        Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": e.to_string()}))).into_response(),
+        Err(e) => (
+            StatusCode::INTERNAL_SERVER_ERROR,
+            Json(json!({"error": e.to_string()})),
+        )
+            .into_response(),
     }
 }
 
 async fn delete_client(Path(id): Path<String>) -> impl IntoResponse {
-    let result = with_db(|conn| {
-        conn.execute("DELETE FROM clients WHERE id = ?1", [&id])
-    });
+    let result = with_db(|conn| conn.execute("DELETE FROM clients WHERE id = ?1", [&id]));
 
     match result {
-        Ok(0) => (StatusCode::NOT_FOUND, Json(json!({"error": "client not found"}))).into_response(),
+        Ok(0) => (
+            StatusCode::NOT_FOUND,
+            Json(json!({"error": "client not found"})),
+        )
+            .into_response(),
         Ok(_) => {
-            audit::log_audit(None, None, audit::CLIENT_DELETED, None, Some(&json!({"client_id": id})));
+            audit::log_audit(
+                None,
+                None,
+                audit::CLIENT_DELETED,
+                None,
+                Some(&json!({"client_id": id})),
+            );
             Json(json!({"ok": true})).into_response()
         }
-        Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": e.to_string()}))).into_response(),
+        Err(e) => (
+            StatusCode::INTERNAL_SERVER_ERROR,
+            Json(json!({"error": e.to_string()})),
+        )
+            .into_response(),
     }
 }
 
@@ -164,11 +198,25 @@ async fn rotate_secret(Path(id): Path<String>) -> impl IntoResponse {
     });
 
     match result {
-        Ok(0) => (StatusCode::NOT_FOUND, Json(json!({"error": "client not found"}))).into_response(),
+        Ok(0) => (
+            StatusCode::NOT_FOUND,
+            Json(json!({"error": "client not found"})),
+        )
+            .into_response(),
         Ok(_) => {
-            audit::log_audit(None, None, audit::CLIENT_SECRET_ROTATED, None, Some(&json!({"client_id": id})));
+            audit::log_audit(
+                None,
+                None,
+                audit::CLIENT_SECRET_ROTATED,
+                None,
+                Some(&json!({"client_id": id})),
+            );
             Json(json!({"id": id, "client_secret": new_secret})).into_response()
         }
-        Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": e.to_string()}))).into_response(),
+        Err(e) => (
+            StatusCode::INTERNAL_SERVER_ERROR,
+            Json(json!({"error": e.to_string()})),
+        )
+            .into_response(),
     }
 }
diff --git a/src/routes/admin/groups.rs b/src/routes/admin/groups.rs
index d86dc82..cf2ff01 100644
--- a/src/routes/admin/groups.rs
+++ b/src/routes/admin/groups.rs
@@ -1,9 +1,9 @@
 use axum::{
+    Json, Router,
     extract::{Path, Query},
     http::StatusCode,
     response::IntoResponse,
     routing::{delete, get, post},
-    Json, Router,
 };
 use serde::{Deserialize, Serialize};
 use serde_json::json;
@@ -49,7 +49,8 @@ async fn list_groups(Query(q): Query<ListQuery>) -> impl IntoResponse {
     let result = with_db(|conn| {
         let (sql, params): (String, Vec<Box<dyn rusqlite::types::ToSql>>) = match &q.tenant_id {
             Some(tid) => (
-                "SELECT id, tenant_id, name, description FROM groups WHERE tenant_id = ?1".to_string(),
+                "SELECT id, tenant_id, name, description FROM groups WHERE tenant_id = ?1"
+                    .to_string(),
                 vec![Box::new(tid.clone()) as Box<dyn rusqlite::types::ToSql>],
             ),
             None => (
@@ -58,7 +59,8 @@ async fn list_groups(Query(q): Query<ListQuery>) -> impl IntoResponse {
             ),
         };
         let mut stmt = conn.prepare(&sql)?;
-        let params_refs: Vec<&dyn rusqlite::types::ToSql> = params.iter().map(|p| p.as_ref()).collect();
+        let params_refs: Vec<&dyn rusqlite::types::ToSql> =
+            params.iter().map(|p| p.as_ref()).collect();
         let rows = stmt.query_map(params_refs.as_slice(), |row| {
             Ok(GroupRow {
                 id: row.get(0)?,
@@ -76,7 +78,11 @@ async fn list_groups(Query(q): Query<ListQuery>) -> impl IntoResponse {
 
     match result {
         Ok(groups) => Json(json!(groups)).into_response(),
-        Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": e.to_string()}))).into_response(),
+        Err(e) => (
+            StatusCode::INTERNAL_SERVER_ERROR,
+            Json(json!({"error": e.to_string()})),
+        )
+            .into_response(),
     }
 }
 
@@ -99,31 +105,55 @@ async fn create_group(Json(body): Json<CreateGroup>) -> impl IntoResponse {
                 None,
                 Some(&json!({"name": body.name, "group_id": id})),
             );
-            (StatusCode::CREATED, Json(json!({"id": id, "name": body.name, "tenant_id": body.tenant_id}))).into_response()
+            (
+                StatusCode::CREATED,
+                Json(json!({"id": id, "name": body.name, "tenant_id": body.tenant_id})),
+            )
+                .into_response()
         }
         Err(e) => {
             let msg = e.to_string();
             if msg.contains("UNIQUE") {
-                (StatusCode::CONFLICT, Json(json!({"error": "group with this name already exists in tenant"}))).into_response()
+                (
+                    StatusCode::CONFLICT,
+                    Json(json!({"error": "group with this name already exists in tenant"})),
+                )
+                    .into_response()
             } else {
-                (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": msg}))).into_response()
+                (
+                    StatusCode::INTERNAL_SERVER_ERROR,
+                    Json(json!({"error": msg})),
+                )
+                    .into_response()
             }
         }
     }
 }
 
 async fn delete_group(Path(id): Path<String>) -> impl IntoResponse {
-    let result = with_db(|conn| {
-        conn.execute("DELETE FROM groups WHERE id = ?1", [&id])
-    });
+    let result = with_db(|conn| conn.execute("DELETE FROM groups WHERE id = ?1", [&id]));
 
     match result {
-        Ok(0) => (StatusCode::NOT_FOUND, Json(json!({"error": "group not found"}))).into_response(),
+        Ok(0) => (
+            StatusCode::NOT_FOUND,
+            Json(json!({"error": "group not found"})),
+        )
+            .into_response(),
         Ok(_) => {
-            audit::log_audit(None, None, audit::GROUP_DELETED, None, Some(&json!({"group_id": id})));
+            audit::log_audit(
+                None,
+                None,
+                audit::GROUP_DELETED,
+                None,
+                Some(&json!({"group_id": id})),
+            );
             Json(json!({"ok": true})).into_response()
         }
-        Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": e.to_string()}))).into_response(),
+        Err(e) => (
+            StatusCode::INTERNAL_SERVER_ERROR,
+            Json(json!({"error": e.to_string()})),
+        )
+            .into_response(),
     }
 }
 
@@ -149,11 +179,23 @@ async fn add_member(Path(id): Path<String>, Json(body): Json<AddMember>) -> impl
         Err(e) => {
             let msg = e.to_string();
             if msg.contains("UNIQUE") || msg.contains("PRIMARY") {
-                (StatusCode::CONFLICT, Json(json!({"error": "user already in group"}))).into_response()
+                (
+                    StatusCode::CONFLICT,
+                    Json(json!({"error": "user already in group"})),
+                )
+                    .into_response()
             } else if msg.contains("FOREIGN") {
-                (StatusCode::BAD_REQUEST, Json(json!({"error": "user or group does not exist"}))).into_response()
+                (
+                    StatusCode::BAD_REQUEST,
+                    Json(json!({"error": "user or group does not exist"})),
+                )
+                    .into_response()
             } else {
-                (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": msg}))).into_response()
+                (
+                    StatusCode::INTERNAL_SERVER_ERROR,
+                    Json(json!({"error": msg})),
+                )
+                    .into_response()
             }
         }
     }
@@ -168,7 +210,11 @@ async fn remove_member(Path((id, user_id)): Path<(String, String)>) -> impl Into
     });
 
     match result {
-        Ok(0) => (StatusCode::NOT_FOUND, Json(json!({"error": "membership not found"}))).into_response(),
+        Ok(0) => (
+            StatusCode::NOT_FOUND,
+            Json(json!({"error": "membership not found"})),
+        )
+            .into_response(),
         Ok(_) => {
             audit::log_audit(
                 None,
@@ -179,6 +225,10 @@ async fn remove_member(Path((id, user_id)): Path<(String, String)>) -> impl Into
             );
             Json(json!({"ok": true})).into_response()
         }
-        Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": e.to_string()}))).into_response(),
+        Err(e) => (
+            StatusCode::INTERNAL_SERVER_ERROR,
+            Json(json!({"error": e.to_string()})),
+        )
+            .into_response(),
     }
 }
diff --git a/src/routes/admin/tenants.rs b/src/routes/admin/tenants.rs
index bc33396..6c0c6d6 100644
--- a/src/routes/admin/tenants.rs
+++ b/src/routes/admin/tenants.rs
@@ -1,9 +1,9 @@
 use axum::{
+    Json, Router,
     extract::Path,
     http::StatusCode,
     response::IntoResponse,
-    routing::{get, patch, post},
-    Json, Router,
+    routing::{get, patch},
 };
 use serde::{Deserialize, Serialize};
 use serde_json::json;
@@ -62,7 +62,11 @@ async fn list_tenants() -> impl IntoResponse {
 
     match result {
         Ok(tenants) => Json(json!(tenants)).into_response(),
-        Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": e.to_string()}))).into_response(),
+        Err(e) => (
+            StatusCode::INTERNAL_SERVER_ERROR,
+            Json(json!({"error": e.to_string()})),
+        )
+            .into_response(),
     }
 }
 
@@ -86,20 +90,35 @@ async fn create_tenant(Json(body): Json<CreateTenant>) -> impl IntoResponse {
                 None,
                 Some(&json!({"name": body.name, "slug": body.slug})),
             );
-            (StatusCode::CREATED, Json(json!({"id": id, "name": body.name, "slug": body.slug}))).into_response()
+            (
+                StatusCode::CREATED,
+                Json(json!({"id": id, "name": body.name, "slug": body.slug})),
+            )
+                .into_response()
         }
         Err(e) => {
             let msg = e.to_string();
             if msg.contains("UNIQUE") {
-                (StatusCode::CONFLICT, Json(json!({"error": "tenant with this slug already exists"}))).into_response()
+                (
+                    StatusCode::CONFLICT,
+                    Json(json!({"error": "tenant with this slug already exists"})),
+                )
+                    .into_response()
             } else {
-                (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": msg}))).into_response()
+                (
+                    StatusCode::INTERNAL_SERVER_ERROR,
+                    Json(json!({"error": msg})),
+                )
+                    .into_response()
             }
         }
     }
 }
 
-async fn update_tenant(Path(id): Path<String>, Json(body): Json<UpdateTenant>) -> impl IntoResponse {
+async fn update_tenant(
+    Path(id): Path<String>,
+    Json(body): Json<UpdateTenant>,
+) -> impl IntoResponse {
     let result = with_db(|conn| {
         let mut sets = Vec::new();
         let mut params: Vec<Box<dyn rusqlite::types::ToSql>> = Vec::new();
@@ -128,12 +147,17 @@ async fn update_tenant(Path(id): Path<String>, Json(body): Json<UpdateTenant>) -
         let sql = format!("UPDATE tenants SET {} WHERE id = ?{idx}", sets.join(", "));
         params.push(Box::new(id.clone()));
 
-        let params_refs: Vec<&dyn rusqlite::types::ToSql> = params.iter().map(|p| p.as_ref()).collect();
+        let params_refs: Vec<&dyn rusqlite::types::ToSql> =
+            params.iter().map(|p| p.as_ref()).collect();
         conn.execute(&sql, params_refs.as_slice())
     });
 
     match result {
-        Ok(0) => (StatusCode::NOT_FOUND, Json(json!({"error": "tenant not found or no changes"}))).into_response(),
+        Ok(0) => (
+            StatusCode::NOT_FOUND,
+            Json(json!({"error": "tenant not found or no changes"})),
+        )
+            .into_response(),
         Ok(_) => {
             audit::log_audit(Some(&id), None, audit::TENANT_UPDATED, None, None);
             Json(json!({"ok": true})).into_response()
@@ -141,25 +165,39 @@ async fn update_tenant(Path(id): Path<String>, Json(body): Json<UpdateTenant>) -
         Err(e) => {
             let msg = e.to_string();
             if msg.contains("UNIQUE") {
-                (StatusCode::CONFLICT, Json(json!({"error": "slug already taken"}))).into_response()
+                (
+                    StatusCode::CONFLICT,
+                    Json(json!({"error": "slug already taken"})),
+                )
+                    .into_response()
             } else {
-                (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": msg}))).into_response()
+                (
+                    StatusCode::INTERNAL_SERVER_ERROR,
+                    Json(json!({"error": msg})),
+                )
+                    .into_response()
             }
         }
     }
 }
 
 async fn delete_tenant(Path(id): Path<String>) -> impl IntoResponse {
-    let result = with_db(|conn| {
-        conn.execute("DELETE FROM tenants WHERE id = ?1", [&id])
-    });
+    let result = with_db(|conn| conn.execute("DELETE FROM tenants WHERE id = ?1", [&id]));
 
     match result {
-        Ok(0) => (StatusCode::NOT_FOUND, Json(json!({"error": "tenant not found"}))).into_response(),
+        Ok(0) => (
+            StatusCode::NOT_FOUND,
+            Json(json!({"error": "tenant not found"})),
+        )
+            .into_response(),
         Ok(_) => {
             audit::log_audit(Some(&id), None, audit::TENANT_DELETED, None, None);
             Json(json!({"ok": true})).into_response()
         }
-        Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": e.to_string()}))).into_response(),
+        Err(e) => (
+            StatusCode::INTERNAL_SERVER_ERROR,
+            Json(json!({"error": e.to_string()})),
+        )
+            .into_response(),
     }
 }
diff --git a/src/routes/admin/users.rs b/src/routes/admin/users.rs
index 28414e6..fa11846 100644
--- a/src/routes/admin/users.rs
+++ b/src/routes/admin/users.rs
@@ -1,9 +1,9 @@
 use axum::{
+    Json, Router,
     extract::{Path, Query},
     http::StatusCode,
     response::IntoResponse,
     routing::{get, post},
-    Json, Router,
 };
 use serde::{Deserialize, Serialize};
 use serde_json::json;
@@ -55,7 +55,10 @@ pub struct ResetPassword {
 pub fn router() -> Router {
     Router::new()
         .route("/", get(list_users).post(create_user))
-        .route("/{id}", get(get_user).patch(update_user).delete(delete_user))
+        .route(
+            "/{id}",
+            get(get_user).patch(update_user).delete(delete_user),
+        )
         .route("/{id}/reset-password", post(reset_password))
         .route("/{id}/lock", post(lock_user))
         .route("/{id}/unlock", post(unlock_user))
@@ -74,7 +77,8 @@ async fn list_users(Query(q): Query<ListQuery>) -> impl IntoResponse {
             ),
         };
         let mut stmt = conn.prepare(&sql)?;
-        let params_refs: Vec<&dyn rusqlite::types::ToSql> = params.iter().map(|p| p.as_ref()).collect();
+        let params_refs: Vec<&dyn rusqlite::types::ToSql> =
+            params.iter().map(|p| p.as_ref()).collect();
         let rows = stmt.query_map(params_refs.as_slice(), |row| {
             Ok(UserRow {
                 id: row.get(0)?,
@@ -98,7 +102,11 @@ async fn list_users(Query(q): Query<ListQuery>) -> impl IntoResponse {
 
     match result {
         Ok(users) => Json(json!(users)).into_response(),
-        Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": e.to_string()}))).into_response(),
+        Err(e) => (
+            StatusCode::INTERNAL_SERVER_ERROR,
+            Json(json!({"error": e.to_string()})),
+        )
+            .into_response(),
     }
 }
 
@@ -126,17 +134,25 @@ async fn get_user(Path(id): Path<String>) -> impl IntoResponse {
 
     match result {
         Ok(user) => Json(json!(user)).into_response(),
-        Err(rusqlite::Error::QueryReturnedNoRows) => {
-            (StatusCode::NOT_FOUND, Json(json!({"error": "user not found"}))).into_response()
-        }
-        Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": e.to_string()}))).into_response(),
+        Err(rusqlite::Error::QueryReturnedNoRows) => (
+            StatusCode::NOT_FOUND,
+            Json(json!({"error": "user not found"})),
+        )
+            .into_response(),
+        Err(e) => (
+            StatusCode::INTERNAL_SERVER_ERROR,
+            Json(json!({"error": e.to_string()})),
+        )
+            .into_response(),
     }
 }
 
 async fn create_user(Json(body): Json<CreateUser>) -> impl IntoResponse {
     let pw_hash = match hash_password(&body.password) {
         Ok(h) => h,
-        Err(e) => return (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": e}))).into_response(),
+        Err(e) => {
+            return (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": e}))).into_response();
+        }
     };
 
     let id = generate_secure_token(16);
@@ -158,14 +174,26 @@ async fn create_user(Json(body): Json<CreateUser>) -> impl IntoResponse {
                 None,
                 Some(&json!({"email": body.email})),
             );
-            (StatusCode::CREATED, Json(json!({"id": id, "email": body.email, "tenant_id": body.tenant_id}))).into_response()
+            (
+                StatusCode::CREATED,
+                Json(json!({"id": id, "email": body.email, "tenant_id": body.tenant_id})),
+            )
+                .into_response()
         }
         Err(e) => {
             let msg = e.to_string();
             if msg.contains("UNIQUE") {
-                (StatusCode::CONFLICT, Json(json!({"error": "user with this email already exists in tenant"}))).into_response()
+                (
+                    StatusCode::CONFLICT,
+                    Json(json!({"error": "user with this email already exists in tenant"})),
+                )
+                    .into_response()
             } else {
-                (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": msg}))).into_response()
+                (
+                    StatusCode::INTERNAL_SERVER_ERROR,
+                    Json(json!({"error": msg})),
+                )
+                    .into_response()
             }
         }
     }
@@ -207,39 +235,59 @@ async fn update_user(Path(id): Path<String>, Json(body): Json<UpdateUser>) -> im
         let sql = format!("UPDATE users SET {} WHERE id = ?{idx}", sets.join(", "));
         params.push(Box::new(id.clone()));
 
-        let params_refs: Vec<&dyn rusqlite::types::ToSql> = params.iter().map(|p| p.as_ref()).collect();
+        let params_refs: Vec<&dyn rusqlite::types::ToSql> =
+            params.iter().map(|p| p.as_ref()).collect();
         conn.execute(&sql, params_refs.as_slice())
     });
 
     match result {
-        Ok(0) => (StatusCode::NOT_FOUND, Json(json!({"error": "user not found or no changes"}))).into_response(),
+        Ok(0) => (
+            StatusCode::NOT_FOUND,
+            Json(json!({"error": "user not found or no changes"})),
+        )
+            .into_response(),
         Ok(_) => {
             audit::log_audit(None, Some(&id), audit::USER_UPDATED, None, None);
             Json(json!({"ok": true})).into_response()
         }
-        Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": e.to_string()}))).into_response(),
+        Err(e) => (
+            StatusCode::INTERNAL_SERVER_ERROR,
+            Json(json!({"error": e.to_string()})),
+        )
+            .into_response(),
     }
 }
 
 async fn delete_user(Path(id): Path<String>) -> impl IntoResponse {
-    let result = with_db(|conn| {
-        conn.execute("DELETE FROM users WHERE id = ?1", [&id])
-    });
+    let result = with_db(|conn| conn.execute("DELETE FROM users WHERE id = ?1", [&id]));
 
     match result {
-        Ok(0) => (StatusCode::NOT_FOUND, Json(json!({"error": "user not found"}))).into_response(),
+        Ok(0) => (
+            StatusCode::NOT_FOUND,
+            Json(json!({"error": "user not found"})),
+        )
+            .into_response(),
         Ok(_) => {
             audit::log_audit(None, Some(&id), audit::USER_DELETED, None, None);
             Json(json!({"ok": true})).into_response()
         }
-        Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": e.to_string()}))).into_response(),
+        Err(e) => (
+            StatusCode::INTERNAL_SERVER_ERROR,
+            Json(json!({"error": e.to_string()})),
+        )
+            .into_response(),
     }
 }
 
-async fn reset_password(Path(id): Path<String>, Json(body): Json<ResetPassword>) -> impl IntoResponse {
+async fn reset_password(
+    Path(id): Path<String>,
+    Json(body): Json<ResetPassword>,
+) -> impl IntoResponse {
     let pw_hash = match hash_password(&body.password) {
         Ok(h) => h,
-        Err(e) => return (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": e}))).into_response(),
+        Err(e) => {
+            return (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": e}))).into_response();
+        }
     };
 
     let now = chrono::Utc::now().timestamp();
@@ -252,12 +300,20 @@ async fn reset_password(Path(id): Path<String>, Json(body): Json<ResetPassword>)
     });
 
     match result {
-        Ok(0) => (StatusCode::NOT_FOUND, Json(json!({"error": "user not found"}))).into_response(),
+        Ok(0) => (
+            StatusCode::NOT_FOUND,
+            Json(json!({"error": "user not found"})),
+        )
+            .into_response(),
         Ok(_) => {
             audit::log_audit(None, Some(&id), audit::USER_PASSWORD_CHANGED, None, None);
             Json(json!({"ok": true})).into_response()
         }
-        Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": e.to_string()}))).into_response(),
+        Err(e) => (
+            StatusCode::INTERNAL_SERVER_ERROR,
+            Json(json!({"error": e.to_string()})),
+        )
+            .into_response(),
     }
 }
 
@@ -274,12 +330,20 @@ async fn lock_user(Path(id): Path<String>) -> impl IntoResponse {
     });
 
     match result {
-        Ok(0) => (StatusCode::NOT_FOUND, Json(json!({"error": "user not found"}))).into_response(),
+        Ok(0) => (
+            StatusCode::NOT_FOUND,
+            Json(json!({"error": "user not found"})),
+        )
+            .into_response(),
         Ok(_) => {
             audit::log_audit(None, Some(&id), audit::USER_LOCKED, None, None);
             Json(json!({"ok": true, "locked_until": locked_until})).into_response()
         }
-        Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": e.to_string()}))).into_response(),
+        Err(e) => (
+            StatusCode::INTERNAL_SERVER_ERROR,
+            Json(json!({"error": e.to_string()})),
+        )
+            .into_response(),
     }
 }
 
@@ -294,11 +358,25 @@ async fn unlock_user(Path(id): Path<String>) -> impl IntoResponse {
     });
 
     match result {
-        Ok(0) => (StatusCode::NOT_FOUND, Json(json!({"error": "user not found"}))).into_response(),
+        Ok(0) => (
+            StatusCode::NOT_FOUND,
+            Json(json!({"error": "user not found"})),
+        )
+            .into_response(),
         Ok(_) => {
-            audit::log_audit(None, Some(&id), audit::USER_UPDATED, None, Some(&json!({"action": "unlock"})));
+            audit::log_audit(
+                None,
+                Some(&id),
+                audit::USER_UPDATED,
+                None,
+                Some(&json!({"action": "unlock"})),
+            );
             Json(json!({"ok": true})).into_response()
         }
-        Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": e.to_string()}))).into_response(),
+        Err(e) => (
+            StatusCode::INTERNAL_SERVER_ERROR,
+            Json(json!({"error": e.to_string()})),
+        )
+            .into_response(),
     }
 }
diff --git a/src/routes/agent.rs b/src/routes/agent.rs
index 7702007..ad9111b 100644
--- a/src/routes/agent.rs
+++ b/src/routes/agent.rs
@@ -28,15 +28,7 @@ pub fn router() -> Router {
     Router::new()
         .route("/agent", get(agent_page).post(agent_submit))
         .route("/agent/kanban", get(agent_kanban_page))
-        .route("/agent/tools", get(agent_tools_page))
         .route("/agent/run", get(agent_run_page))
-        .route("/agent/tasks", get(agent_tasks_page))
-        .route("/agent/projects", get(agent_projects_page))
-        .route("/agent/agy", get(agent_agy_page))
-        .route("/agent/notes", get(agent_notes_page))
-        .route("/agent/passvault", get(agent_passvault_page))
-        .route("/agent/cron", get(agent_cron_page))
-        .route("/agent/curator", get(agent_curator_page))
         .route("/agent/kanban/boards", get(kanban_boards))
         .route("/agent/kanban/board/{board}", get(kanban_board))
         .route("/agent/kanban/tasks", post(kanban_create_task))
@@ -130,45 +122,10 @@ async fn agent_kanban_page(headers: HeaderMap, Extension(user): Extension<AuthUs
     agent_workspace_page(headers, user, AgentPage::Kanban).await
 }
 
-async fn agent_tools_page(headers: HeaderMap, Extension(user): Extension<AuthUser>) -> Response {
-    agent_workspace_page(headers, user, AgentPage::Tools).await
-}
-
 async fn agent_run_page(headers: HeaderMap, Extension(user): Extension<AuthUser>) -> Response {
     agent_workspace_page(headers, user, AgentPage::Run).await
 }
 
-async fn agent_tasks_page(headers: HeaderMap, Extension(user): Extension<AuthUser>) -> Response {
-    agent_workspace_page(headers, user, AgentPage::Tasks).await
-}
-
-async fn agent_projects_page(headers: HeaderMap, Extension(user): Extension<AuthUser>) -> Response {
-    agent_workspace_page(headers, user, AgentPage::Projects).await
-}
-
-async fn agent_agy_page(headers: HeaderMap, Extension(user): Extension<AuthUser>) -> Response {
-    agent_workspace_page(headers, user, AgentPage::Agy).await
-}
-
-async fn agent_notes_page(headers: HeaderMap, Extension(user): Extension<AuthUser>) -> Response {
-    agent_workspace_page(headers, user, AgentPage::Notes).await
-}
-
-async fn agent_passvault_page(
-    headers: HeaderMap,
-    Extension(user): Extension<AuthUser>,
-) -> Response {
-    agent_workspace_page(headers, user, AgentPage::Passvault).await
-}
-
-async fn agent_cron_page(headers: HeaderMap, Extension(user): Extension<AuthUser>) -> Response {
-    agent_workspace_page(headers, user, AgentPage::Cron).await
-}
-
-async fn agent_curator_page(headers: HeaderMap, Extension(user): Extension<AuthUser>) -> Response {
-    agent_workspace_page(headers, user, AgentPage::Curator).await
-}
-
 async fn agent_workspace_page(headers: HeaderMap, user: AuthUser, page: AgentPage) -> Response {
     if !agent_allowed(&user.email) {
         return forbidden_page(&headers, &user);
@@ -545,7 +502,7 @@ async fn request_json(method: &str, url: &str, body: Option<&str>) -> Result<Str
         method = method,
         path = target.path,
         host = target.host_header(),
-        len = body.as_bytes().len(),
+        len = body.len(),
         body = body,
     );
     stream
diff --git a/src/routes/agent_os.css b/src/routes/agent_os.css
index bdb336c..f6299ba 100644
--- a/src/routes/agent_os.css
+++ b/src/routes/agent_os.css
@@ -460,62 +460,13 @@ body {
   border-color: color-mix(in srgb, var(--accent) 38%, var(--border));
 }
 
-.agent-panel-grid,
-.agent-panel-list {
-  display: grid;
-  gap: 8px;
-}
-
-.agent-tool-launcher {
-  display: grid;
-  grid-template-columns: repeat(2, minmax(0, 1fr));
-  gap: 8px;
-}
-
-.agent-tool-launcher button,
-.agent-tool-launcher a {
-  appearance: none;
-  min-width: 0;
-  display: grid;
-  gap: 4px;
-  min-height: 78px;
-  padding: 11px;
-  border: 1px solid var(--soft-border);
-  border-radius: 12px;
-  background: color-mix(in srgb, var(--bg-2) 68%, transparent);
-  color: var(--fg);
-  font: inherit;
-  text-align: left;
-  text-decoration: none;
-  cursor: pointer;
-}
-
-.agent-tool-launcher button:hover,
-.agent-tool-launcher a:hover {
-  border-color: color-mix(in srgb, var(--accent) 38%, var(--border));
-  background: color-mix(in srgb, var(--panel) 68%, var(--bg));
-}
-
-.agent-tool-launcher b {
-  font-size: 13px;
-}
-
-.agent-tool-launcher span {
-  color: var(--muted);
-  font-size: 12px;
-  line-height: 1.35;
-}
-
-.agent-route-launcher {
-  grid-template-columns: repeat(4, minmax(0, 1fr));
-}
-
 .agent-panel-grid {
+  display: grid;
+  gap: 8px;
   grid-template-columns: repeat(2, minmax(0, 1fr));
 }
 
-.agent-panel-grid div,
-.agent-panel-list article {
+.agent-panel-grid div {
   min-width: 0;
   padding: 10px;
   border: 1px solid var(--soft-border);
@@ -537,21 +488,6 @@ body {
   overflow-wrap: anywhere;
 }
 
-.agent-panel-list article {
-  display: grid;
-  gap: 3px;
-}
-
-.agent-panel-list b {
-  color: var(--fg);
-  font-weight: 650;
-}
-
-.agent-panel-list span {
-  color: var(--muted);
-  font-size: 13px;
-}
-
 .agent-panel-actions {
   display: flex;
   flex-wrap: wrap;
@@ -559,18 +495,6 @@ body {
   margin-top: 14px;
 }
 
-.agent-notes-editor {
-  min-height: 150px;
-  width: 100%;
-  padding: 10px;
-  border: 1px solid var(--soft-border);
-  border-radius: 12px;
-  background: color-mix(in srgb, var(--bg-2) 68%, transparent);
-  color: var(--fg);
-  font: 14px/1.55 var(--font-sans);
-  resize: vertical;
-}
-
 .kanban-shell {
   display: grid;
   gap: 12px;
@@ -942,14 +866,6 @@ body {
     grid-template-columns: 1fr;
   }
 
-  .agent-tool-launcher {
-    grid-template-columns: 1fr;
-  }
-
-  .agent-route-launcher {
-    grid-template-columns: 1fr;
-  }
-
   .agent-page-scroll {
     padding: 16px;
   }
diff --git a/src/routes/agent_view.rs b/src/routes/agent_view.rs
index 4dd3ccb..bec5762 100644
--- a/src/routes/agent_view.rs
+++ b/src/routes/agent_view.rs
@@ -9,15 +9,7 @@ pub const AGENT_OS_STYLES: &str = include_str!("agent_os.css");
 pub(crate) enum AgentPage {
     Chat,
     Kanban,
-    Tools,
     Run,
-    Tasks,
-    Projects,
-    Agy,
-    Notes,
-    Passvault,
-    Cron,
-    Curator,
 }
 
 impl AgentPage {
@@ -25,15 +17,7 @@ impl AgentPage {
         match self {
             Self::Chat => "Current conversation",
             Self::Kanban => "Kanban",
-            Self::Tools => "Tools",
             Self::Run => "Run details",
-            Self::Tasks => "Tasks",
-            Self::Projects => "Projects",
-            Self::Agy => "AGY context",
-            Self::Notes => "Notes",
-            Self::Passvault => "Passvault",
-            Self::Cron => "Cron",
-            Self::Curator => "Curator",
         }
     }
 
@@ -41,15 +25,7 @@ impl AgentPage {
         match self {
             Self::Chat => "/agent",
             Self::Kanban => "/agent/kanban",
-            Self::Tools => "/agent/tools",
             Self::Run => "/agent/run",
-            Self::Tasks => "/agent/tasks",
-            Self::Projects => "/agent/projects",
-            Self::Agy => "/agent/agy",
-            Self::Notes => "/agent/notes",
-            Self::Passvault => "/agent/passvault",
-            Self::Cron => "/agent/cron",
-            Self::Curator => "/agent/curator",
         }
     }
 }
@@ -162,33 +138,12 @@ fn render_sidebar(active: AgentPage) -> String {
       <nav class="agent-nav" aria-label="Workspace">
         {chat}
         {kanban}
-        {tools}
         {run}
       </nav>
-    </div>
-    <div class="agent-section">
-      <h2>Agent surfaces</h2>
-      <nav class="agent-nav" aria-label="Agent surfaces">
-        {tasks}
-        {projects}
-        {agy}
-        {notes}
-        {passvault}
-        {cron}
-        {curator}
-      </nav>
     </div>"#,
         chat = nav_link(AgentPage::Chat, active, Some("now")),
         kanban = nav_link(AgentPage::Kanban, active, Some("board")),
-        tools = nav_link(AgentPage::Tools, active, None),
         run = nav_link(AgentPage::Run, active, None),
-        tasks = nav_link(AgentPage::Tasks, active, None),
-        projects = nav_link(AgentPage::Projects, active, None),
-        agy = nav_link(AgentPage::Agy, active, None),
-        notes = nav_link(AgentPage::Notes, active, None),
-        passvault = nav_link(AgentPage::Passvault, active, None),
-        cron = nav_link(AgentPage::Cron, active, None),
-        curator = nav_link(AgentPage::Curator, active, None),
     )
 }
 
@@ -206,7 +161,6 @@ fn render_chat_main(prompt: &str, outcome: Option<&AgentOutcome>, csrf: &str) ->
       </div>
       <div class="agent-meta" aria-label="Runtime">
         <a href="/agent/kanban">Kanban</a>
-        <a href="/agent/tools">Tools</a>
         <a href="/agent/run">Run details</a>
       </div>
     </header>
@@ -242,7 +196,6 @@ fn render_workspace_main(user: &AuthUser, page: AgentPage, provider: &str, model
       <div class="agent-meta" aria-label="Runtime">
         <a href="/agent">Chat</a>
         <a href="/agent/kanban">Kanban</a>
-        <a href="/agent/tools">Tools</a>
       </div>
     </header>
     <div class="agent-page-scroll">
@@ -264,7 +217,7 @@ fn render_ready_timeline() -> String {
   <div class="agent-suggestions" aria-label="Suggestions">
     <button type="button" data-agent-prompt="Inspect the current agent status and tell me the next concrete fixes.">Inspect agent</button>
     <button type="button" data-agent-prompt="Plan the next agent deploy. Include risks, checks, and rollback.">Plan a deploy</button>
-    <a href="/agent/tools">Open tools</a>
+    <a href="/agent/kanban">Open kanban</a>
   </div>
 </section>"#
         .to_string()
@@ -341,70 +294,10 @@ fn render_page_content(user: &AuthUser, page: AgentPage, provider: &str, model:
     match page {
         AgentPage::Chat => String::new(),
         AgentPage::Kanban => render_kanban_page(),
-        AgentPage::Tools => render_tools_page(),
         AgentPage::Run => render_run_page(user, provider, model),
-        AgentPage::Tasks => render_tasks_page(),
-        AgentPage::Projects => render_projects_page(),
-        AgentPage::Agy => render_agy_page(),
-        AgentPage::Notes => render_notes_page(),
-        AgentPage::Passvault => render_passvault_page(),
-        AgentPage::Cron => render_cron_page(),
-        AgentPage::Curator => render_curator_page(),
     }
 }
 
-fn render_tools_page() -> String {
-    let tools = [
-        (
-            AgentPage::Tasks,
-            "Plan, create, and review work without covering the chat.",
-        ),
-        (
-            AgentPage::Projects,
-            "Open AkurAI workspaces connected to this agent.",
-        ),
-        (
-            AgentPage::Agy,
-            "Review durable personal context for the current tenant.",
-        ),
-        (
-            AgentPage::Notes,
-            "Save local working notes for this browser session.",
-        ),
-        (
-            AgentPage::Passvault,
-            "Prepare confirmation-gated credential requests.",
-        ),
-        (AgentPage::Cron, "Draft scheduled checks and follow-ups."),
-        (
-            AgentPage::Kanban,
-            "Open the Rust Agent board, task, and claim workflow.",
-        ),
-        (
-            AgentPage::Curator,
-            "Find unfinished UI, noisy controls, and cleanup work.",
-        ),
-    ];
-    let links = tools
-        .into_iter()
-        .map(|(page, description)| {
-            format!(
-                r#"<a href="{path}"><b>{label}</b><span>{description}</span></a>"#,
-                path = page.path(),
-                label = page.title(),
-                description = esc_html(description),
-            )
-        })
-        .collect::<Vec<_>>()
-        .join("");
-    format!(
-        r#"<div class="agent-panel-head">
-    <div><h2>Agent tools</h2><p>Use these pages when the conversation needs context, notes, credentials, or work queues.</p></div>
-  </div>
-  <div class="agent-tool-launcher agent-route-launcher">{links}</div>"#
-    )
-}
-
 fn render_run_page(user: &AuthUser, provider: &str, model: &str) -> String {
     let session_id = agent_session_id(user);
     let scope_id = agent_scope_id(user);
@@ -431,96 +324,6 @@ fn render_run_page(user: &AuthUser, provider: &str, model: &str) -> String {
     )
 }
 
-fn render_tasks_page() -> String {
-    r#"<div class="agent-panel-head">
-    <div><h2>Tasks</h2><p>Work queue for this tenant agent.</p></div>
-  </div>
-  <div class="agent-panel-list">
-    <article><b>Review open work</b><span>Summarize active agent tasks and gaps.</span></article>
-    <article><b>Create from chat</b><span>Turn the current conversation into a concrete task.</span></article>
-    <article><b>Confirm before action</b><span>Collect approvals before destructive or credentialed steps.</span></article>
-  </div>
-  <div class="agent-panel-actions">
-    <button type="button" data-agent-prompt="Review my open agent tasks and group them by priority.">Review tasks</button>
-    <button type="button" data-agent-prompt="Create a concise task plan from this conversation and wait for confirmation.">Create task</button>
-  </div>"#
-        .to_string()
-}
-
-fn render_projects_page() -> String {
-    r#"<div class="agent-panel-head">
-    <div><h2>Projects</h2><p>AkurAI workspaces connected to this agent.</p></div>
-  </div>
-  <div class="agent-panel-list">
-    <article><b>Agent Core</b><span>Gateway, CLI/TUI, and persistent AGY context.</span></article>
-    <article><b>AkurAI IDP</b><span>Agent console, login, tenant routing, auth surface.</span></article>
-    <article><b>AkurAI Framework</b><span>Shared theme registry and console components.</span></article>
-  </div>
-  <div class="agent-panel-actions">
-    <button type="button" data-agent-prompt="Inspect the agent core project and report the highest-impact incomplete UI/runtime work.">Inspect project</button>
-    <button type="button" data-agent-prompt="Compare AkurAI IDP and agent core UI responsibilities and suggest the clean boundary.">Check boundary</button>
-  </div>"#
-        .to_string()
-}
-
-fn render_agy_page() -> String {
-    r#"<div class="agent-panel-head">
-    <div><h2>AGY context</h2><p>Personal agent context, durable notes, and preferences.</p></div>
-  </div>
-  <div class="agent-panel-list">
-    <article><b>Identity</b><span>Tenant agent for olibuijr@olibuijr.com.</span></article>
-    <article><b>Unique features</b><span>Persistent AGY context, notes, and passvault-aware requests.</span></article>
-    <article><b>Operating rule</b><span>Ask for confirmation when a tool action needs approval.</span></article>
-  </div>
-  <div class="agent-panel-actions">
-    <button type="button" data-agent-prompt="Load AGY context for this tenant and summarize the durable preferences that matter now.">Summarize AGY</button>
-    <button type="button" data-agent-prompt="Use AGY context, notes, and passvault boundaries before planning the next step.">Use context</button>
-  </div>"#
-        .to_string()
-}
-
-fn render_notes_page() -> String {
-    r#"<div class="agent-panel-head">
-    <div><h2>Notes</h2><p>Local working notes for this browser session.</p></div>
-  </div>
-  <textarea class="agent-notes-editor" data-notes-editor placeholder="Write working notes for this agent..."></textarea>
-  <div class="agent-panel-actions">
-    <button type="button" data-save-notes>Save notes</button>
-    <button type="button" data-use-notes>Use notes</button>
-  </div>
-  <p class="agent-panel-status" data-notes-status></p>"#
-        .to_string()
-}
-
-fn render_passvault_page() -> String {
-    r#"<div class="agent-panel-head">
-    <div><h2>Passvault</h2><p>Credential requests stay confirmation-gated.</p></div>
-  </div>
-  <div class="agent-panel-list">
-    <article><b>Sealed by default</b><span>No credential is shown in the UI.</span></article>
-    <article><b>Confirm first</b><span>The agent should ask before using a secret.</span></article>
-  </div>
-  <div class="agent-panel-actions">
-    <button type="button" data-agent-prompt="Prepare a passvault-backed request. Ask me exactly what secret or confirmation you need before using it.">Request secret</button>
-    <button type="button" data-agent-prompt="Audit whether the next task needs passvault access. If yes, ask for confirmation first.">Check access</button>
-  </div>"#
-        .to_string()
-}
-
-fn render_cron_page() -> String {
-    r#"<div class="agent-panel-head">
-    <div><h2>Cron</h2><p>Scheduled agent work.</p></div>
-  </div>
-  <div class="agent-panel-list">
-    <article><b>Daily health</b><span>Check deploy state, gateway health, and parity drift.</span></article>
-    <article><b>Follow-up sweep</b><span>Find stale tasks and unfinished UI surfaces.</span></article>
-  </div>
-  <div class="agent-panel-actions">
-    <button type="button" data-agent-prompt="Draft a cron schedule for agent health checks and ask before enabling anything.">Draft schedule</button>
-  </div>"#
-        .to_string()
-}
-
 fn render_kanban_page() -> String {
     r#"<div class="agent-panel-head">
     <div><h2>Kanban</h2><p>Rust Agent board, tasks, claims, and project delivery state.</p></div>
@@ -556,21 +359,6 @@ fn render_kanban_page() -> String {
         .to_string()
 }
 
-fn render_curator_page() -> String {
-    r#"<div class="agent-panel-head">
-    <div><h2>Curator</h2><p>Cleanup and quality pass.</p></div>
-  </div>
-  <div class="agent-panel-list">
-    <article><b>Remove dead UI</b><span>Find controls that do not map to a real workflow.</span></article>
-    <article><b>Tighten copy</b><span>Reduce status noise and internal implementation labels.</span></article>
-    <article><b>Verify live</b><span>Use screenshots and smoke prompts after deploy.</span></article>
-  </div>
-  <div class="agent-panel-actions">
-    <button type="button" data-agent-prompt="Act as curator: identify UI bloat, dead controls, and the smallest fixes to make this console feel finished.">Run curator</button>
-  </div>"#
-        .to_string()
-}
-
 fn agent_scope_id(user: &AuthUser) -> String {
     format!("idp:{}", user.tenant_id)
 }
@@ -600,10 +388,9 @@ mod tests {
     fn body_contains_agent_os_surfaces() {
         let html = agent_body(&user(), "csrf", "", None);
         assert!(html.contains("AkurAI"));
-        assert!(html.contains("Passvault"));
         assert!(html.contains("Kanban"));
         assert!(html.contains(r#"href="/agent/kanban""#));
-        assert!(html.contains(r#"href="/agent/notes""#));
+        assert!(html.contains(r#"href="/agent/run""#));
         assert!(!html.contains("data-panel-trigger"));
         assert!(!html.contains("data-agent-panel"));
         assert!(!html.contains("data-panel-template"));
@@ -613,6 +400,19 @@ mod tests {
         assert!(html.contains("tool_call"));
     }
 
+    #[test]
+    fn sidebar_drops_agent_surfaces_slop() {
+        let html = agent_body(&user(), "csrf", "", None);
+        assert!(!html.contains("Agent surfaces"));
+        assert!(!html.contains(r#"href="/agent/tasks""#));
+        assert!(!html.contains(r#"href="/agent/projects""#));
+        assert!(!html.contains(r#"href="/agent/notes""#));
+        assert!(!html.contains(r#"href="/agent/passvault""#));
+        assert!(!html.contains(r#"href="/agent/cron""#));
+        assert!(!html.contains(r#"href="/agent/curator""#));
+        assert!(!html.contains(r#"href="/agent/tools""#));
+    }
+
     #[test]
     fn kanban_body_contains_board_on_own_page() {
         let html = agent_static_page_body(&user(), "csrf", AgentPage::Kanban);
@@ -624,10 +424,11 @@ mod tests {
     }
 
     #[test]
-    fn notes_body_uses_route_not_overlay_template() {
-        let html = agent_static_page_body(&user(), "csrf", AgentPage::Notes);
-        assert!(html.contains(r#"data-agent-page="/agent/notes""#));
-        assert!(html.contains("data-use-notes"));
+    fn run_body_uses_route_and_shows_real_runtime() {
+        let html = agent_static_page_body(&user(), "csrf", AgentPage::Run);
+        assert!(html.contains(r#"data-agent-page="/agent/run""#));
+        assert!(html.contains("Current run"));
+        assert!(html.contains("olibuijr@olibuijr.com"));
         assert!(!html.contains("data-panel-template"));
     }
 
diff --git a/src/routes/auth_pages.rs b/src/routes/auth_pages.rs
index e63655e..6b726a9 100644
--- a/src/routes/auth_pages.rs
+++ b/src/routes/auth_pages.rs
@@ -136,10 +136,10 @@ async fn login_submit(headers: HeaderMap, Form(form): Form<LoginForm>) -> Respon
     let now = now_epoch();
 
     // Check lockout
-    if let Some(locked_until) = user.locked_until {
-        if locked_until > now {
-            return login_redirect("Account temporarily locked. Try again later.", &return_to);
-        }
+    if let Some(locked_until) = user.locked_until
+        && locked_until > now
+    {
+        return login_redirect("Account temporarily locked. Try again later.", &return_to);
     }
 
     // Verify password
@@ -483,10 +483,10 @@ fn safe_return_to(input: Option<&str>) -> String {
 
     // Absolute URL: must match our base_url origin
     let base = &config::get().base_url;
-    if let Some(origin) = extract_origin(base) {
-        if val.starts_with(&origin) {
-            return val.to_string();
-        }
+    if let Some(origin) = extract_origin(base)
+        && val.starts_with(&origin)
+    {
+        return val.to_string();
     }
 
     "/".to_string()
diff --git a/src/routes/authorize.rs b/src/routes/authorize.rs
index dcf8f8b..265dd0d 100644
--- a/src/routes/authorize.rs
+++ b/src/routes/authorize.rs
@@ -38,7 +38,14 @@ async fn authorize_endpoint(
     // Validate required params
     let client_id = match &params.client_id {
         Some(c) if !c.is_empty() => c.clone(),
-        _ => return auth_error_redirect(None, params.state.as_deref(), "invalid_request", "Missing client_id"),
+        _ => {
+            return auth_error_redirect(
+                None,
+                params.state.as_deref(),
+                "invalid_request",
+                "Missing client_id",
+            );
+        }
     };
 
     let redirect_uri = match &params.redirect_uri {
@@ -100,15 +107,15 @@ async fn authorize_endpoint(
     }
 
     // PKCE validation (require S256 if code_challenge provided)
-    if let Some(ref method) = params.code_challenge_method {
-        if method != "S256" {
-            return auth_error_redirect(
-                Some(&redirect_uri),
-                params.state.as_deref(),
-                "invalid_request",
-                "Only S256 code_challenge_method is supported",
-            );
-        }
+    if let Some(ref method) = params.code_challenge_method
+        && method != "S256"
+    {
+        return auth_error_redirect(
+            Some(&redirect_uri),
+            params.state.as_deref(),
+            "invalid_request",
+            "Only S256 code_challenge_method is supported",
+        );
     }
     if params.code_challenge.is_some() && params.code_challenge_method.is_none() {
         return auth_error_redirect(
@@ -136,13 +143,18 @@ async fn authorize_endpoint(
                 urlencoded(requested_scopes),
                 urlencoded(params.state.as_deref().unwrap_or("")),
                 urlencoded(params.nonce.as_deref().unwrap_or("")),
-                params.code_challenge.as_ref().map(|c|
-                    format!("&code_challenge={}&code_challenge_method={}",
+                params
+                    .code_challenge
+                    .as_ref()
+                    .map(|c| format!(
+                        "&code_challenge={}&code_challenge_method={}",
                         urlencoded(c),
-                        urlencoded(params.code_challenge_method.as_deref().unwrap_or("S256")))
-                ).unwrap_or_default(),
+                        urlencoded(params.code_challenge_method.as_deref().unwrap_or("S256"))
+                    ))
+                    .unwrap_or_default(),
             );
-            return Redirect::to(&format!("/login?return_to={}", urlencoded(&return_url))).into_response();
+            return Redirect::to(&format!("/login?return_to={}", urlencoded(&return_url)))
+                .into_response();
         }
     };
 
@@ -252,7 +264,10 @@ fn auth_error_redirect(
 ) -> axum::response::Response {
     match redirect_uri {
         Some(uri) => {
-            let mut location = format!("{uri}?error={error}&error_description={}", urlencoded(description));
+            let mut location = format!(
+                "{uri}?error={error}&error_description={}",
+                urlencoded(description)
+            );
             if let Some(s) = state {
                 location.push_str(&format!("&state={}", urlencoded(s)));
             }
@@ -268,7 +283,12 @@ fn auth_error_page(description: &str) -> axum::response::Response {
 <html><head><title>Authorization Error</title></head>
 <body><h1>Authorization Error</h1><p>{description}</p></body></html>"#,
     );
-    (StatusCode::BAD_REQUEST, [("content-type", "text/html")], html).into_response()
+    (
+        StatusCode::BAD_REQUEST,
+        [("content-type", "text/html")],
+        html,
+    )
+        .into_response()
 }
 
 fn urlencoded(s: &str) -> String {
diff --git a/src/routes/introspect.rs b/src/routes/introspect.rs
index 71c31d8..f33a428 100644
--- a/src/routes/introspect.rs
+++ b/src/routes/introspect.rs
@@ -1,4 +1,6 @@
-use axum::{Json, Router, extract::Request, http::StatusCode, response::IntoResponse, routing::post};
+use axum::{
+    Json, Router, extract::Request, http::StatusCode, response::IntoResponse, routing::post,
+};
 use serde::Deserialize;
 use serde_json::{Value, json};
 
@@ -27,8 +29,8 @@ async fn introspect_endpoint(request: Request) -> impl IntoResponse {
         Err(_) => return (StatusCode::OK, Json(json!({"active": false}))).into_response(),
     };
 
-    let mut params: IntrospectRequest = serde_json::from_slice(&body_bytes)
-        .unwrap_or_else(|_| parse_form_urlencoded(&body_bytes));
+    let mut params: IntrospectRequest =
+        serde_json::from_slice(&body_bytes).unwrap_or_else(|_| parse_form_urlencoded(&body_bytes));
 
     // Basic auth overrides body params
     if let Some(id) = basic_id {
@@ -39,7 +41,11 @@ async fn introspect_endpoint(request: Request) -> impl IntoResponse {
     }
 
     if params.client_id.is_none() {
-        return (StatusCode::UNAUTHORIZED, Json(json!({"error": "invalid_client"}))).into_response();
+        return (
+            StatusCode::UNAUTHORIZED,
+            Json(json!({"error": "invalid_client"})),
+        )
+            .into_response();
     }
 
     let token = match params.token {
diff --git a/src/routes/revoke.rs b/src/routes/revoke.rs
index 107ac5c..21e5d86 100644
--- a/src/routes/revoke.rs
+++ b/src/routes/revoke.rs
@@ -1,4 +1,6 @@
-use axum::{Json, Router, extract::Request, http::StatusCode, response::IntoResponse, routing::post};
+use axum::{
+    Json, Router, extract::Request, http::StatusCode, response::IntoResponse, routing::post,
+};
 use serde::Deserialize;
 use serde_json::json;
 
@@ -26,8 +28,8 @@ async fn revoke_endpoint(request: Request) -> impl IntoResponse {
         Err(_) => return StatusCode::OK.into_response(),
     };
 
-    let mut params: RevokeRequest = serde_json::from_slice(&body_bytes)
-        .unwrap_or_else(|_| parse_form_urlencoded(&body_bytes));
+    let mut params: RevokeRequest =
+        serde_json::from_slice(&body_bytes).unwrap_or_else(|_| parse_form_urlencoded(&body_bytes));
 
     if let Some(id) = basic_id {
         params.client_id = Some(id);
@@ -38,7 +40,11 @@ async fn revoke_endpoint(request: Request) -> impl IntoResponse {
 
     // Client auth required
     if params.client_id.is_none() {
-        return (StatusCode::UNAUTHORIZED, Json(json!({"error": "invalid_client"}))).into_response();
+        return (
+            StatusCode::UNAUTHORIZED,
+            Json(json!({"error": "invalid_client"})),
+        )
+            .into_response();
     }
 
     let token = match params.token {
diff --git a/src/routes/token.rs b/src/routes/token.rs
index 9dd8e34..1401042 100644
--- a/src/routes/token.rs
+++ b/src/routes/token.rs
@@ -12,7 +12,7 @@ use std::time::{SystemTime, UNIX_EPOCH};
 use crate::config;
 use crate::db::with_db;
 use crate::lib::audit::{self, log_audit_event};
-use crate::lib::crypto::{generate_secure_token, sha256, constant_time_equal};
+use crate::lib::crypto::{constant_time_equal, generate_secure_token, sha256};
 use crate::lib::jwt::sign_token;
 use crate::lib::pkce::verify_code_challenge;
 
@@ -41,11 +41,17 @@ async fn token_endpoint(request: Request) -> impl IntoResponse {
     // Parse body as form or JSON
     let body_bytes = match axum::body::to_bytes(request.into_body(), 1024 * 64).await {
         Ok(b) => b,
-        Err(_) => return token_error(StatusCode::BAD_REQUEST, "invalid_request", "Failed to read body"),
+        Err(_) => {
+            return token_error(
+                StatusCode::BAD_REQUEST,
+                "invalid_request",
+                "Failed to read body",
+            );
+        }
     };
 
-    let mut params: TokenRequest = serde_json::from_slice(&body_bytes)
-        .unwrap_or_else(|_| parse_form_urlencoded(&body_bytes));
+    let mut params: TokenRequest =
+        serde_json::from_slice(&body_bytes).unwrap_or_else(|_| parse_form_urlencoded(&body_bytes));
 
     // Basic auth overrides body params for client credentials
     if let Some(id) = basic_client_id {
@@ -57,14 +63,24 @@ async fn token_endpoint(request: Request) -> impl IntoResponse {
 
     let grant_type = match &params.grant_type {
         Some(gt) => gt.as_str(),
-        None => return token_error(StatusCode::BAD_REQUEST, "invalid_request", "Missing grant_type"),
+        None => {
+            return token_error(
+                StatusCode::BAD_REQUEST,
+                "invalid_request",
+                "Missing grant_type",
+            );
+        }
     };
 
     match grant_type {
         "authorization_code" => handle_authorization_code(params, &ip).await,
         "refresh_token" => handle_refresh_token(params, &ip).await,
         "client_credentials" => handle_client_credentials(params, &ip).await,
-        _ => token_error(StatusCode::BAD_REQUEST, "unsupported_grant_type", "Unsupported grant_type"),
+        _ => token_error(
+            StatusCode::BAD_REQUEST,
+            "unsupported_grant_type",
+            "Unsupported grant_type",
+        ),
     }
 }
 
@@ -75,11 +91,23 @@ async fn handle_authorization_code(params: TokenRequest, ip: &str) -> axum::resp
     };
     let client_id = match &params.client_id {
         Some(c) => c.clone(),
-        None => return token_error(StatusCode::BAD_REQUEST, "invalid_request", "Missing client_id"),
+        None => {
+            return token_error(
+                StatusCode::BAD_REQUEST,
+                "invalid_request",
+                "Missing client_id",
+            );
+        }
     };
     let redirect_uri = match &params.redirect_uri {
         Some(r) => r.clone(),
-        None => return token_error(StatusCode::BAD_REQUEST, "invalid_request", "Missing redirect_uri"),
+        None => {
+            return token_error(
+                StatusCode::BAD_REQUEST,
+                "invalid_request",
+                "Missing redirect_uri",
+            );
+        }
     };
 
     let now = now_secs();
@@ -111,7 +139,13 @@ async fn handle_authorization_code(params: TokenRequest, ip: &str) -> axum::resp
 
     let auth_code = match auth_code {
         Some(ac) => ac,
-        None => return token_error(StatusCode::BAD_REQUEST, "invalid_grant", "Invalid authorization code"),
+        None => {
+            return token_error(
+                StatusCode::BAD_REQUEST,
+                "invalid_grant",
+                "Invalid authorization code",
+            );
+        }
     };
 
     // Validate
@@ -124,21 +158,41 @@ async fn handle_authorization_code(params: TokenRequest, ip: &str) -> axum::resp
             )
             .ok();
         });
-        return token_error(StatusCode::BAD_REQUEST, "invalid_grant", "Authorization code already used");
+        return token_error(
+            StatusCode::BAD_REQUEST,
+            "invalid_grant",
+            "Authorization code already used",
+        );
     }
     if auth_code.expires_at < now {
-        return token_error(StatusCode::BAD_REQUEST, "invalid_grant", "Authorization code expired");
+        return token_error(
+            StatusCode::BAD_REQUEST,
+            "invalid_grant",
+            "Authorization code expired",
+        );
     }
     if auth_code.client_id != client_id {
-        return token_error(StatusCode::BAD_REQUEST, "invalid_grant", "Client ID mismatch");
+        return token_error(
+            StatusCode::BAD_REQUEST,
+            "invalid_grant",
+            "Client ID mismatch",
+        );
     }
     if auth_code.redirect_uri != redirect_uri {
-        return token_error(StatusCode::BAD_REQUEST, "invalid_grant", "Redirect URI mismatch");
+        return token_error(
+            StatusCode::BAD_REQUEST,
+            "invalid_grant",
+            "Redirect URI mismatch",
+        );
     }
 
     // Verify client secret
     if !verify_client_secret(&client_id, params.client_secret.as_deref()) {
-        return token_error(StatusCode::UNAUTHORIZED, "invalid_client", "Invalid client credentials");
+        return token_error(
+            StatusCode::UNAUTHORIZED,
+            "invalid_client",
+            "Invalid client credentials",
+        );
     }
 
     // PKCE verification
@@ -146,17 +200,30 @@ async fn handle_authorization_code(params: TokenRequest, ip: &str) -> axum::resp
         let method = auth_code.code_challenge_method.as_deref().unwrap_or("S256");
         let verifier = match &params.code_verifier {
             Some(v) => v,
-            None => return token_error(StatusCode::BAD_REQUEST, "invalid_request", "Missing code_verifier"),
+            None => {
+                return token_error(
+                    StatusCode::BAD_REQUEST,
+                    "invalid_request",
+                    "Missing code_verifier",
+                );
+            }
         };
         if !verify_code_challenge(verifier, challenge, method) {
-            return token_error(StatusCode::BAD_REQUEST, "invalid_grant", "PKCE verification failed");
+            return token_error(
+                StatusCode::BAD_REQUEST,
+                "invalid_grant",
+                "PKCE verification failed",
+            );
         }
     }
 
     // Mark code as used
     with_db(|conn| {
-        conn.execute("UPDATE auth_codes SET used = 1 WHERE code = ?1", rusqlite::params![code])
-            .ok();
+        conn.execute(
+            "UPDATE auth_codes SET used = 1 WHERE code = ?1",
+            rusqlite::params![code],
+        )
+        .ok();
     });
 
     // Load user for token claims
@@ -199,17 +266,35 @@ async fn handle_authorization_code(params: TokenRequest, ip: &str) -> axum::resp
 
     let (kid, private_key) = match get_active_signing_key() {
         Some(k) => k,
-        None => return token_error(StatusCode::INTERNAL_SERVER_ERROR, "server_error", "No signing key available"),
+        None => {
+            return token_error(
+                StatusCode::INTERNAL_SERVER_ERROR,
+                "server_error",
+                "No signing key available",
+            );
+        }
     };
 
     let access_token = match sign_token(&access_token_claims, &private_key, &kid, 3600) {
         Ok(t) => t,
-        Err(_) => return token_error(StatusCode::INTERNAL_SERVER_ERROR, "server_error", "Failed to sign token"),
+        Err(_) => {
+            return token_error(
+                StatusCode::INTERNAL_SERVER_ERROR,
+                "server_error",
+                "Failed to sign token",
+            );
+        }
     };
 
     let id_token = match sign_token(&id_token_claims, &private_key, &kid, 3600) {
         Ok(t) => t,
-        Err(_) => return token_error(StatusCode::INTERNAL_SERVER_ERROR, "server_error", "Failed to sign token"),
+        Err(_) => {
+            return token_error(
+                StatusCode::INTERNAL_SERVER_ERROR,
+                "server_error",
+                "Failed to sign token",
+            );
+        }
     };
 
     // Issue refresh token if offline_access scope
@@ -260,15 +345,31 @@ async fn handle_authorization_code(params: TokenRequest, ip: &str) -> axum::resp
 async fn handle_refresh_token(params: TokenRequest, ip: &str) -> axum::response::Response {
     let rt = match &params.refresh_token {
         Some(t) => t.clone(),
-        None => return token_error(StatusCode::BAD_REQUEST, "invalid_request", "Missing refresh_token"),
+        None => {
+            return token_error(
+                StatusCode::BAD_REQUEST,
+                "invalid_request",
+                "Missing refresh_token",
+            );
+        }
     };
     let client_id = match &params.client_id {
         Some(c) => c.clone(),
-        None => return token_error(StatusCode::BAD_REQUEST, "invalid_request", "Missing client_id"),
+        None => {
+            return token_error(
+                StatusCode::BAD_REQUEST,
+                "invalid_request",
+                "Missing client_id",
+            );
+        }
     };
 
     if !verify_client_secret(&client_id, params.client_secret.as_deref()) {
-        return token_error(StatusCode::UNAUTHORIZED, "invalid_client", "Invalid client credentials");
+        return token_error(
+            StatusCode::UNAUTHORIZED,
+            "invalid_client",
+            "Invalid client credentials",
+        );
     }
 
     let rt_hash = sha256(&rt);
@@ -296,17 +397,35 @@ async fn handle_refresh_token(params: TokenRequest, ip: &str) -> axum::response:
 
     let stored = match stored {
         Some(s) => s,
-        None => return token_error(StatusCode::BAD_REQUEST, "invalid_grant", "Invalid refresh token"),
+        None => {
+            return token_error(
+                StatusCode::BAD_REQUEST,
+                "invalid_grant",
+                "Invalid refresh token",
+            );
+        }
     };
 
     if stored.revoked {
-        return token_error(StatusCode::BAD_REQUEST, "invalid_grant", "Refresh token revoked");
+        return token_error(
+            StatusCode::BAD_REQUEST,
+            "invalid_grant",
+            "Refresh token revoked",
+        );
     }
     if stored.expires_at < now {
-        return token_error(StatusCode::BAD_REQUEST, "invalid_grant", "Refresh token expired");
+        return token_error(
+            StatusCode::BAD_REQUEST,
+            "invalid_grant",
+            "Refresh token expired",
+        );
     }
     if stored.client_id != client_id {
-        return token_error(StatusCode::BAD_REQUEST, "invalid_grant", "Client ID mismatch");
+        return token_error(
+            StatusCode::BAD_REQUEST,
+            "invalid_grant",
+            "Client ID mismatch",
+        );
     }
 
     // Rotate: revoke old, issue new
@@ -363,16 +482,34 @@ async fn handle_refresh_token(params: TokenRequest, ip: &str) -> axum::response:
 
     let (kid, private_key) = match get_active_signing_key() {
         Some(k) => k,
-        None => return token_error(StatusCode::INTERNAL_SERVER_ERROR, "server_error", "No signing key"),
+        None => {
+            return token_error(
+                StatusCode::INTERNAL_SERVER_ERROR,
+                "server_error",
+                "No signing key",
+            );
+        }
     };
 
     let access_token = match sign_token(&access_token_claims, &private_key, &kid, 3600) {
         Ok(t) => t,
-        Err(_) => return token_error(StatusCode::INTERNAL_SERVER_ERROR, "server_error", "Sign failed"),
+        Err(_) => {
+            return token_error(
+                StatusCode::INTERNAL_SERVER_ERROR,
+                "server_error",
+                "Sign failed",
+            );
+        }
     };
     let id_token = match sign_token(&id_token_claims, &private_key, &kid, 3600) {
         Ok(t) => t,
-        Err(_) => return token_error(StatusCode::INTERNAL_SERVER_ERROR, "server_error", "Sign failed"),
+        Err(_) => {
+            return token_error(
+                StatusCode::INTERNAL_SERVER_ERROR,
+                "server_error",
+                "Sign failed",
+            );
+        }
     };
 
     let audit_meta = json!({"grant_type": "refresh_token", "client_id": client_id});
@@ -402,11 +539,21 @@ async fn handle_refresh_token(params: TokenRequest, ip: &str) -> axum::response:
 async fn handle_client_credentials(params: TokenRequest, ip: &str) -> axum::response::Response {
     let client_id = match &params.client_id {
         Some(c) => c.clone(),
-        None => return token_error(StatusCode::BAD_REQUEST, "invalid_request", "Missing client_id"),
+        None => {
+            return token_error(
+                StatusCode::BAD_REQUEST,
+                "invalid_request",
+                "Missing client_id",
+            );
+        }
     };
 
     if !verify_client_secret(&client_id, params.client_secret.as_deref()) {
-        return token_error(StatusCode::UNAUTHORIZED, "invalid_client", "Invalid client credentials");
+        return token_error(
+            StatusCode::UNAUTHORIZED,
+            "invalid_client",
+            "Invalid client credentials",
+        );
     }
 
     // Verify client supports client_credentials grant
@@ -428,12 +575,22 @@ async fn handle_client_credentials(params: TokenRequest, ip: &str) -> axum::resp
 
     let client = match client {
         Some(c) => c,
-        None => return token_error(StatusCode::UNAUTHORIZED, "invalid_client", "Client not found"),
+        None => {
+            return token_error(
+                StatusCode::UNAUTHORIZED,
+                "invalid_client",
+                "Client not found",
+            );
+        }
     };
 
     let grant_types = crate::lib::parse_json_or_space_separated(&client.grant_types);
     if !grant_types.iter().any(|g| g == "client_credentials") {
-        return token_error(StatusCode::BAD_REQUEST, "unauthorized_client", "Grant type not allowed");
+        return token_error(
+            StatusCode::BAD_REQUEST,
+            "unauthorized_client",
+            "Grant type not allowed",
+        );
     }
 
     let requested_scope = params.scope.as_deref().unwrap_or(&client.scopes);
@@ -452,12 +609,24 @@ async fn handle_client_credentials(params: TokenRequest, ip: &str) -> axum::resp
 
     let (kid, private_key) = match get_active_signing_key() {
         Some(k) => k,
-        None => return token_error(StatusCode::INTERNAL_SERVER_ERROR, "server_error", "No signing key"),
+        None => {
+            return token_error(
+                StatusCode::INTERNAL_SERVER_ERROR,
+                "server_error",
+                "No signing key",
+            );
+        }
     };
 
     let access_token = match sign_token(&access_token_claims, &private_key, &kid, 3600) {
         Ok(t) => t,
-        Err(_) => return token_error(StatusCode::INTERNAL_SERVER_ERROR, "server_error", "Sign failed"),
+        Err(_) => {
+            return token_error(
+                StatusCode::INTERNAL_SERVER_ERROR,
+                "server_error",
+                "Sign failed",
+            );
+        }
     };
 
     let audit_meta = json!({"grant_type": "client_credentials", "client_id": client_id});
diff --git a/src/routes/userinfo.rs b/src/routes/userinfo.rs
index 9f39d9c..cb781eb 100644
--- a/src/routes/userinfo.rs
+++ b/src/routes/userinfo.rs
@@ -1,7 +1,7 @@
-use axum::{Json, Router, routing::get};
 use axum::extract::Request;
 use axum::http::StatusCode;
 use axum::response::IntoResponse;
+use axum::{Json, Router, routing::get};
 use serde_json::{Value, json};
 
 use crate::db::with_db;
diff --git a/src/routes/well_known.rs b/src/routes/well_known.rs
index ad040dd..73c52f7 100644
--- a/src/routes/well_known.rs
+++ b/src/routes/well_known.rs
@@ -7,7 +7,10 @@ use crate::lib::jwt::export_public_key_jwk;
 
 pub fn router() -> Router {
     Router::new()
-        .route("/.well-known/openid-configuration", get(openid_configuration))
+        .route(
+            "/.well-known/openid-configuration",
+            get(openid_configuration),
+        )
         .route("/jwks", get(jwks))
 }
 
@@ -45,9 +48,7 @@ async fn jwks() -> Json<Value> {
         })
         .expect("failed to query signing_keys")
         .filter_map(|r| r.ok())
-        .filter_map(|(kid, public_key, _alg)| {
-            export_public_key_jwk(&public_key, &kid).ok()
-        })
+        .filter_map(|(kid, public_key, _alg)| export_public_key_jwk(&public_key, &kid).ok())
         .collect()
     });