diff --git a/src/channels/web/handlers/users.rs b/src/channels/web/handlers/users.rs index f7f4a93c..66b5f515 100644 --- a/src/channels/web/handlers/users.rs +++ b/src/channels/web/handlers/users.rs @@ -48,7 +48,6 @@ pub async fn users_create_handler( )); } - tracing::info!("users_create: passed validation, building record"); let user_id = Uuid::new_v4().to_string(); let now = chrono::Utc::now(); @@ -68,15 +67,10 @@ pub async fn users_create_handler( metadata: serde_json::json!({}), }; - tracing::info!("users_create: calling create_user for {}", user_id); store .create_user(&user_record) .await - .map_err(|e| { - tracing::error!("users_create: create_user failed: {e}"); - (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()) - })?; - tracing::info!("users_create: create_user succeeded"); + .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; // Generate a first API token so the new user can authenticate immediately. // Hash the hex-encoded plaintext (what the user sends as Bearer token), @@ -87,15 +81,10 @@ pub async fn users_create_handler( let token_hash = crate::channels::web::auth::hash_token(&plaintext_token); let token_prefix = &plaintext_token[..8]; - tracing::info!("users_create: calling create_api_token"); let _token_record = store .create_api_token(&user_id, "initial", &token_hash, token_prefix, None) .await - .map_err(|e| { - tracing::error!("users_create: create_api_token failed: {e}"); - (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()) - })?; - tracing::info!("users_create: complete, returning response"); + .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; Ok(Json(serde_json::json!({ "id": user_record.id, diff --git a/src/channels/web/mod.rs b/src/channels/web/mod.rs index e267825a..82c16b93 100644 --- a/src/channels/web/mod.rs +++ b/src/channels/web/mod.rs @@ -73,7 +73,7 @@ impl GatewayChannel { /// /// If no auth token is configured, generates a random one and prints it. /// Builds a single-user `MultiAuthState` from the config. - pub fn new(config: GatewayConfig) -> Self { + pub fn new(config: GatewayConfig, owner_id: String) -> Self { let auth_token = config.auth_token.clone().unwrap_or_else(|| { use rand::RngCore; use rand::rngs::OsRng; @@ -83,7 +83,7 @@ impl GatewayChannel { }); let auth = CombinedAuthState { - env_auth: MultiAuthState::single(auth_token, config.user_id.clone()), + env_auth: MultiAuthState::single(auth_token, owner_id.clone()), db_auth: None, }; @@ -101,8 +101,7 @@ impl GatewayChannel { job_manager: None, prompt_queue: None, scheduler: None, - owner_id: config.user_id.clone(), - default_sender_id: config.user_id.clone(), + owner_id, shutdown_tx: tokio::sync::RwLock::new(None), ws_tracker: Some(Arc::new(ws::WsConnectionTracker::new())), llm_provider: None, @@ -126,18 +125,6 @@ impl GatewayChannel { } } - /// Rebind the single-user auth identity to the durable owner scope while - /// preserving the configured gateway sender/routing identity. - pub fn with_owner_scope(mut self, owner_id: impl Into) -> Self { - let owner_id = owner_id.into(); - let single_user_token = self.auth.env_auth.first_token().map(ToOwned::to_owned); - if let Some(token) = single_user_token { - self.auth.env_auth = MultiAuthState::single(token, owner_id.clone()); - } - self.rebuild_state(|s| s.owner_id = owner_id); - self - } - /// Helper to rebuild state, copying existing fields and applying a mutation. fn rebuild_state(&mut self, mutate: impl FnOnce(&mut GatewayState)) { let mut new_state = GatewayState { @@ -156,7 +143,6 @@ impl GatewayChannel { prompt_queue: self.state.prompt_queue.clone(), scheduler: self.state.scheduler.clone(), owner_id: self.state.owner_id.clone(), - default_sender_id: self.state.default_sender_id.clone(), shutdown_tx: tokio::sync::RwLock::new(None), ws_tracker: self.state.ws_tracker.clone(), llm_provider: self.state.llm_provider.clone(), diff --git a/src/channels/web/server.rs b/src/channels/web/server.rs index 824d1521..3661c09f 100644 --- a/src/channels/web/server.rs +++ b/src/channels/web/server.rs @@ -347,8 +347,6 @@ pub struct GatewayState { pub prompt_queue: Option, /// Durable owner scope for persistence and unauthenticated callback flows. pub owner_id: String, - /// Default sender/routing identity for gateway-originated messages. - pub default_sender_id: String, /// Shutdown signal sender. pub shutdown_tx: tokio::sync::RwLock>>, /// WebSocket connection tracker. @@ -407,42 +405,6 @@ pub async fn start_server( // Public routes (no auth) let public = Router::new() .route("/api/health", get(health_handler)) - .route("/api/debug/db-write", get({ - let dbg_state = state.clone(); - move || async move { - tracing::info!("debug/db-write: starting"); - let store = match dbg_state.store.as_ref() { - Some(s) => s, - None => return "ERROR: store is None".to_string(), - }; - tracing::info!("debug/db-write: store is Some, attempting create_user"); - let id = format!("dbg-{}", uuid::Uuid::new_v4()); - let now = chrono::Utc::now(); - let user = crate::db::UserRecord { - id: id.clone(), - email: None, - display_name: "debug-test".to_string(), - status: "active".to_string(), - role: "member".to_string(), - created_at: now, - updated_at: now, - last_login_at: None, - created_by: None, - metadata: serde_json::json!({}), - }; - match store.create_user(&user).await { - Ok(()) => { - tracing::info!("debug/db-write: create_user succeeded"); - let _ = store.delete_user(&id).await; - format!("OK: created and deleted user {id}") - } - Err(e) => { - tracing::error!("debug/db-write: create_user failed: {e}"); - format!("ERROR: {e}") - } - } - } - })) .route("/oauth/callback", get(oauth_callback_handler)) .route( "/oauth/slack/callback", @@ -1413,9 +1375,6 @@ async fn chat_send_handler( } let mut msg = IncomingMessage::new("gateway", &user.user_id, &req.content); - if state.owner_id != state.default_sender_id && user.user_id == state.owner_id { - msg = msg.with_sender_id(&state.default_sender_id); - } // Prefer timezone from JSON body, fall back to X-Timezone header let tz = req .timezone @@ -1517,9 +1476,6 @@ async fn chat_approval_handler( })?; let mut msg = IncomingMessage::new("gateway", &user.user_id, content); - if state.owner_id != state.default_sender_id && user.user_id == state.owner_id { - msg = msg.with_sender_id(&state.default_sender_id); - } if let Some(ref thread_id) = req.thread_id { msg = msg.with_thread(thread_id); @@ -3095,7 +3051,6 @@ mod tests { job_manager: None, prompt_queue: None, owner_id: "test".to_string(), - default_sender_id: "test".to_string(), shutdown_tx: tokio::sync::RwLock::new(None), ws_tracker: None, llm_provider: None, diff --git a/src/channels/web/test_helpers.rs b/src/channels/web/test_helpers.rs index 00c02467..90948fcc 100644 --- a/src/channels/web/test_helpers.rs +++ b/src/channels/web/test_helpers.rs @@ -77,7 +77,6 @@ impl TestGatewayBuilder { job_manager: None, prompt_queue: None, owner_id: self.user_id.clone(), - default_sender_id: self.user_id, shutdown_tx: tokio::sync::RwLock::new(None), ws_tracker: Some(Arc::new(WsConnectionTracker::new())), llm_provider: self.llm_provider, diff --git a/src/channels/web/tests/multi_tenant.rs b/src/channels/web/tests/multi_tenant.rs index 470dd2cf..7bc4687d 100644 --- a/src/channels/web/tests/multi_tenant.rs +++ b/src/channels/web/tests/multi_tenant.rs @@ -16,7 +16,6 @@ use axum::routing::{delete, get, post}; use tower::ServiceExt; use uuid::Uuid; -use crate::channels::web::GatewayChannel; use crate::channels::web::auth::{ AuthenticatedUser, MultiAuthState, UserIdentity, auth_middleware, }; @@ -24,7 +23,6 @@ use crate::channels::web::server::{ ActiveConfigSnapshot, GatewayState, PerUserRateLimiter, PromptQueue, RateLimiter, WorkspacePool, }; use crate::channels::web::sse::SseManager; -use crate::config::GatewayConfig; // ── Helpers ──────────────────────────────────────────────────────────── @@ -69,7 +67,6 @@ fn build_state( job_manager: None, prompt_queue, owner_id: "test".to_string(), - default_sender_id: "test".to_string(), shutdown_tx: tokio::sync::RwLock::new(None), ws_tracker: None, llm_provider: None, @@ -88,40 +85,6 @@ fn build_state( }) } -fn gateway_config() -> GatewayConfig { - GatewayConfig { - host: "127.0.0.1".to_string(), - port: 3000, - auth_token: Some("gateway-auth".to_string()), - user_id: "gateway-sender".to_string(), - workspace_read_scopes: Vec::new(), - memory_layers: Vec::new(), - } -} - -#[test] -fn with_owner_scope_updates_gateway_owner_scope_in_multi_user_mode() { - let mut gateway = GatewayChannel::new(gateway_config()); - gateway.auth = two_user_auth().into(); - let gateway = gateway.with_owner_scope("owner-scope"); - - assert_eq!(gateway.state.owner_id, "owner-scope"); - assert_eq!(gateway.state.default_sender_id, "gateway-sender"); - - let alice = gateway - .auth - .env_auth - .authenticate("tok-alice") - .expect("alice token should remain valid"); - let bob = gateway - .auth - .env_auth - .authenticate("tok-bob") - .expect("bob token should remain valid"); - assert_eq!(alice.user_id, "alice"); - assert_eq!(bob.user_id, "bob"); -} - /// Create a libSQL-backed test database in a temporary directory. /// /// Returns the database and a `TempDir` guard — the database file is diff --git a/src/channels/web/ws.rs b/src/channels/web/ws.rs index 5e10a9d8..24396ea9 100644 --- a/src/channels/web/ws.rs +++ b/src/channels/web/ws.rs @@ -521,7 +521,6 @@ mod tests { prompt_queue: None, scheduler: None, owner_id: "test".to_string(), - default_sender_id: "test".to_string(), shutdown_tx: tokio::sync::RwLock::new(None), ws_tracker: Some(Arc::new(WsConnectionTracker::new())), llm_provider: None, diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 611d7247..011ef449 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -352,7 +352,8 @@ pub async fn run_routines_cli( .await .map_err(|e| anyhow::anyhow!("{e:#}"))?; - let user_id = std::env::var("GATEWAY_USER_ID").unwrap_or_else(|_| "default".to_string()); + let user_id = + std::env::var("IRONCLAW_OWNER_ID").unwrap_or_else(|_| "default".to_string()); run_routines_command(routines_cmd.clone(), db, &user_id).await } diff --git a/src/config/channels.rs b/src/config/channels.rs index 159201c7..504817f3 100644 --- a/src/config/channels.rs +++ b/src/config/channels.rs @@ -43,7 +43,6 @@ pub struct GatewayConfig { pub port: u16, /// Bearer token for authentication. Random hex generated at startup if unset. pub auth_token: Option, - pub user_id: String, /// Additional user scopes for workspace reads. /// /// When set, the workspace will be able to read (search, read, list) from @@ -118,10 +117,6 @@ impl ChannelsConfig { let gateway_enabled = parse_bool_env("GATEWAY_ENABLED", cs.gateway_enabled)?; let gateway = if gateway_enabled { - let user_id = optional_env("GATEWAY_USER_ID")? - .or_else(|| cs.gateway_user_id.clone()) - .unwrap_or_else(|| owner_id.to_string()); - let memory_layers: Vec = match optional_env("MEMORY_LAYERS")? { Some(json_str) => { @@ -130,7 +125,7 @@ impl ChannelsConfig { message: format!("must be valid JSON array of layer objects: {e}"), })? } - None => crate::workspace::layer::MemoryLayer::default_for_user(&user_id), + None => crate::workspace::layer::MemoryLayer::default_for_user(owner_id), }; // Validate layer names and scopes @@ -209,7 +204,6 @@ impl ChannelsConfig { )?, auth_token: optional_env("GATEWAY_AUTH_TOKEN")? .or_else(|| cs.gateway_auth_token.clone()), - user_id, workspace_read_scopes, memory_layers, }) @@ -366,14 +360,12 @@ mod tests { host: "127.0.0.1".to_string(), port: 3000, auth_token: Some("tok-abc".to_string()), - user_id: "default".to_string(), workspace_read_scopes: vec![], memory_layers: vec![], }; assert_eq!(cfg.host, "127.0.0.1"); assert_eq!(cfg.port, 3000); assert_eq!(cfg.auth_token.as_deref(), Some("tok-abc")); - assert_eq!(cfg.user_id, "default"); } #[test] @@ -382,7 +374,6 @@ mod tests { host: "0.0.0.0".to_string(), port: 3001, auth_token: None, - user_id: "anon".to_string(), workspace_read_scopes: vec![], memory_layers: vec![], }; @@ -511,7 +502,6 @@ mod tests { assert_eq!(gateway.host, "127.0.0.3"); assert_eq!(gateway.port, 9191); assert_eq!(gateway.auth_token.as_deref(), Some("tok")); - assert_eq!(gateway.user_id, "owner-scope"); let signal = cfg.signal.expect("signal config"); assert_eq!(signal.account, "+15551234567"); diff --git a/src/main.rs b/src/main.rs index 8224d507..9059d6e9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -591,8 +591,7 @@ async fn async_main() -> anyhow::Result<()> { let mut gateway_url: Option = None; let mut sse_manager: Option> = None; if let Some(ref gw_config) = config.channels.gateway { - let mut gw = GatewayChannel::new(gw_config.clone()); - gw = gw.with_owner_scope(config.owner_id.clone()); + let mut gw = GatewayChannel::new(gw_config.clone(), config.owner_id.clone()); gw = gw.with_llm_provider(Arc::clone(&components.llm)); if let Some(ref ws) = components.workspace { gw = gw.with_workspace(Arc::clone(ws)); @@ -641,9 +640,9 @@ async fn async_main() -> anyhow::Result<()> { if let Ok(false) = d.has_any_users().await { let now = chrono::Utc::now(); let user = ironclaw::db::UserRecord { - id: gw_config.user_id.clone(), + id: config.owner_id.clone(), email: None, - display_name: gw_config.user_id.clone(), + display_name: config.owner_id.clone(), status: "active".to_string(), role: "admin".to_string(), created_at: now, @@ -667,14 +666,14 @@ async fn async_main() -> anyhow::Result<()> { auth_token }; if let Err(e) = d - .create_api_token(&gw_config.user_id, "bootstrap", &hash, prefix, None) + .create_api_token(&config.owner_id, "bootstrap", &hash, prefix, None) .await { tracing::warn!("Failed to create bootstrap token: {}", e); } } tracing::info!( - user_id = gw_config.user_id, + user_id = config.owner_id, "Bootstrapped admin user from gateway config" ); } @@ -820,12 +819,7 @@ async fn async_main() -> anyhow::Result<()> { .await; // Default user ID for extension operations (single-user mode). - let ext_user_id = config - .channels - .gateway - .as_ref() - .map(|g| g.user_id.clone()) - .unwrap_or_else(|| "default".to_string()); + let ext_user_id = config.owner_id.clone(); // Wire up channel runtime for hot-activation of WASM channels. if let Some(ref ext_mgr) = components.extension_manager diff --git a/src/settings.rs b/src/settings.rs index 1bb1a8f7..09d9d9d0 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -269,10 +269,6 @@ pub struct ChannelSettings { #[serde(default)] pub gateway_auth_token: Option, - /// Web gateway user ID. - #[serde(default)] - pub gateway_user_id: Option, - /// Whether the CLI channel is enabled. #[serde(default = "default_true")] pub cli_enabled: bool, @@ -342,7 +338,6 @@ impl Default for ChannelSettings { gateway_host: None, gateway_port: None, gateway_auth_token: None, - gateway_user_id: None, cli_enabled: true, signal_enabled: false, signal_http_url: None, diff --git a/src/tunnel/mod.rs b/src/tunnel/mod.rs index 13d00f83..e73fcd46 100644 --- a/src/tunnel/mod.rs +++ b/src/tunnel/mod.rs @@ -428,7 +428,6 @@ mod tests { host: "127.0.0.1".to_string(), port: 3000, auth_token: None, - user_id: "test".to_string(), workspace_read_scopes: Vec::new(), memory_layers: Vec::new(), }); @@ -441,7 +440,6 @@ mod tests { host: host.to_string(), port, auth_token: None, - user_id: "test".to_string(), workspace_read_scopes: Vec::new(), memory_layers: Vec::new(), }); diff --git a/tests/e2e/conftest.py b/tests/e2e/conftest.py index aa8ba1cb..9f4b02f3 100644 --- a/tests/e2e/conftest.py +++ b/tests/e2e/conftest.py @@ -271,7 +271,6 @@ async def ironclaw_server( "GATEWAY_HOST": "127.0.0.1", "GATEWAY_PORT": str(gateway_port), "GATEWAY_AUTH_TOKEN": AUTH_TOKEN, - "GATEWAY_USER_ID": "e2e-web-sender", "HTTP_HOST": "127.0.0.1", "HTTP_PORT": str(http_port), "HTTP_WEBHOOK_SECRET": HTTP_WEBHOOK_SECRET, @@ -371,7 +370,6 @@ async def hosted_oauth_refresh_server( "GATEWAY_HOST": "127.0.0.1", "GATEWAY_PORT": str(gateway_port), "GATEWAY_AUTH_TOKEN": AUTH_TOKEN, - "GATEWAY_USER_ID": OWNER_SCOPE_ID, "HTTP_HOST": "127.0.0.1", "HTTP_PORT": str(http_port), "HTTP_WEBHOOK_SECRET": HTTP_WEBHOOK_SECRET, @@ -411,7 +409,6 @@ async def hosted_oauth_refresh_server( yield { "base_url": base_url, "db_path": db_path, - "gateway_user_id": OWNER_SCOPE_ID, "mock_llm_url": mock_llm_server, } except TimeoutError: @@ -473,7 +470,6 @@ async def http_channel_server_without_secret( "GATEWAY_HOST": "127.0.0.1", "GATEWAY_PORT": str(gateway_port), "GATEWAY_AUTH_TOKEN": AUTH_TOKEN, - "GATEWAY_USER_ID": "e2e-tester", "HTTP_HOST": "127.0.0.1", "HTTP_PORT": str(http_port), "CLI_ENABLED": "false", diff --git a/tests/multi_tenant_integration.rs b/tests/multi_tenant_integration.rs index f4ce7353..f76e2abf 100644 --- a/tests/multi_tenant_integration.rs +++ b/tests/multi_tenant_integration.rs @@ -42,8 +42,6 @@ const ALICE_USER_ID: &str = "alice"; const BOB_USER_ID: &str = "bob"; const OWNER_TOKEN: &str = "tok-owner-secret"; const OWNER_SCOPE_ID: &str = "owner-scope"; -const GATEWAY_SENDER_ID: &str = "gateway-sender"; - /// Build a MultiAuthState with two users. fn two_user_auth() -> MultiAuthState { let mut tokens = HashMap::new(); @@ -549,7 +547,6 @@ fn gateway_state_has_multi_tenant_fields() { prompt_queue: None, scheduler: None, owner_id: "fallback".to_string(), - default_sender_id: "fallback".to_string(), shutdown_tx: tokio::sync::RwLock::new(None), ws_tracker: Some(Arc::new(WsConnectionTracker::new())), llm_provider: None, @@ -567,7 +564,6 @@ fn gateway_state_has_multi_tenant_fields() { }; assert_eq!(state.owner_id, "fallback"); - assert_eq!(state.default_sender_id, "fallback"); assert!(state.workspace_pool.is_none()); } @@ -626,7 +622,6 @@ async fn start_owner_scoped_sender_server() -> ( prompt_queue: None, scheduler: None, owner_id: OWNER_SCOPE_ID.to_string(), - default_sender_id: GATEWAY_SENDER_ID.to_string(), shutdown_tx: tokio::sync::RwLock::new(None), ws_tracker: Some(Arc::new(WsConnectionTracker::new())), llm_provider: None, @@ -778,7 +773,7 @@ async fn full_server_chat_send_rewrites_sender_only_for_owner_scope_rebind() { .expect("Timed out waiting for owner message") .expect("Agent channel closed"); assert_eq!(owner_msg.user_id, OWNER_SCOPE_ID); - assert_eq!(owner_msg.sender_id, GATEWAY_SENDER_ID); + assert_eq!(owner_msg.sender_id, OWNER_SCOPE_ID); assert_eq!(owner_msg.content, "hello from owner"); let other_resp = client @@ -1012,7 +1007,6 @@ async fn start_multi_user_server_with_db() -> ( prompt_queue: None, scheduler: None, owner_id: ALICE_USER_ID.to_string(), - default_sender_id: ALICE_USER_ID.to_string(), shutdown_tx: tokio::sync::RwLock::new(None), ws_tracker: Some(Arc::new(WsConnectionTracker::new())), llm_provider: None, diff --git a/tests/openai_compat_integration.rs b/tests/openai_compat_integration.rs index b7aed757..be28a6a5 100644 --- a/tests/openai_compat_integration.rs +++ b/tests/openai_compat_integration.rs @@ -205,7 +205,6 @@ async fn start_test_server_with_provider( prompt_queue: None, scheduler: None, owner_id: "test-user".to_string(), - default_sender_id: "test-user".to_string(), shutdown_tx: tokio::sync::RwLock::new(None), ws_tracker: Some(Arc::new(WsConnectionTracker::new())), llm_provider: Some(llm_provider), @@ -705,7 +704,6 @@ async fn test_no_llm_provider_returns_503() { prompt_queue: None, scheduler: None, owner_id: "test-user".to_string(), - default_sender_id: "test-user".to_string(), shutdown_tx: tokio::sync::RwLock::new(None), ws_tracker: Some(Arc::new(WsConnectionTracker::new())), llm_provider: None, // No LLM! diff --git a/tests/support/gateway_workflow_harness.rs b/tests/support/gateway_workflow_harness.rs index d6d02cd5..1d510399 100644 --- a/tests/support/gateway_workflow_harness.rs +++ b/tests/support/gateway_workflow_harness.rs @@ -227,7 +227,6 @@ impl GatewayWorkflowHarness { prompt_queue: None, scheduler: Some(scheduler_slot.clone()), owner_id: user_id.clone(), - default_sender_id: user_id.clone(), shutdown_tx: tokio::sync::RwLock::new(None), ws_tracker: Some(Arc::new(WsConnectionTracker::new())), llm_provider: Some(Arc::clone(&components.llm)), diff --git a/tests/ws_gateway_integration.rs b/tests/ws_gateway_integration.rs index 4fc6cd8f..1068a123 100644 --- a/tests/ws_gateway_integration.rs +++ b/tests/ws_gateway_integration.rs @@ -52,7 +52,6 @@ async fn start_test_server() -> ( prompt_queue: None, scheduler: None, owner_id: "test-user".to_string(), - default_sender_id: "test-user".to_string(), shutdown_tx: tokio::sync::RwLock::new(None), ws_tracker: Some(Arc::new(WsConnectionTracker::new())), llm_provider: None,