diff --git a/crates/ironclaw_architecture/tests/reborn_inmemory_store_ratchet.rs b/crates/ironclaw_architecture/tests/reborn_inmemory_store_ratchet.rs index d48b01b96e8..e5e4499d0c1 100644 --- a/crates/ironclaw_architecture/tests/reborn_inmemory_store_ratchet.rs +++ b/crates/ironclaw_architecture/tests/reborn_inmemory_store_ratchet.rs @@ -59,7 +59,6 @@ const FROZEN_INMEMORY_STORES: &[&str] = &[ // --- peripheral stores (outside §4.3's five core domains; listed so the // ratchet stays exhaustive and no new InMemory store slips in) --- "InMemoryBoundedSubagentGoalStore", - "InMemoryDeliveredGateRouteStore", "InMemoryExtensionInstallationStore", "InMemoryOpenAiCompatRefStore", "InMemorySecretStore", diff --git a/crates/ironclaw_channel_delivery/src/tests.rs b/crates/ironclaw_channel_delivery/src/tests.rs index cf57d3686ce..78b55287f28 100644 --- a/crates/ironclaw_channel_delivery/src/tests.rs +++ b/crates/ironclaw_channel_delivery/src/tests.rs @@ -143,7 +143,7 @@ mod tests { OutboundDeliveryTargetEntry, OutboundDeliveryTargetProvider, }; use ironclaw_outbound::{ - CommunicationPreferenceRecord, DeliveryDefaultScope, InMemoryDeliveredGateRouteStore, + CommunicationPreferenceRecord, DeliveryDefaultScope, WriteCommunicationPreferenceRequest, }; @@ -895,7 +895,7 @@ mod tests { thread_service, turn_coordinator: coordinator, outbound_store: outbound.clone(), - route_store: Arc::new(InMemoryDeliveredGateRouteStore::default()), + route_store: Arc::new(in_memory_backed_outbound_state_store()), communication_preferences: outbound, adapter: test_adapter(installation_id), egress, @@ -1013,7 +1013,7 @@ mod tests { ); let delivery_store = Arc::new(in_memory_backed_outbound_state_store()); - let route_store = Arc::new(InMemoryDeliveredGateRouteStore::default()); + let route_store = Arc::new(in_memory_backed_outbound_state_store()); let services = make_services( coordinator, thread_service, @@ -1131,7 +1131,7 @@ mod tests { egress.allow_credential_handle("slack_bot_token"); let delivery_store = Arc::new(in_memory_backed_outbound_state_store()); - let route_store = Arc::new(InMemoryDeliveredGateRouteStore::default()); + let route_store = Arc::new(in_memory_backed_outbound_state_store()); let services = make_services( coordinator, thread_service, @@ -1269,7 +1269,7 @@ mod tests { ); let delivery_store = Arc::new(in_memory_backed_outbound_state_store()); - let route_store = Arc::new(InMemoryDeliveredGateRouteStore::default()); + let route_store = Arc::new(in_memory_backed_outbound_state_store()); let services = make_services( coordinator, thread_service, @@ -1348,7 +1348,7 @@ mod tests { ); let delivery_store = Arc::new(in_memory_backed_outbound_state_store()); - let route_store = Arc::new(InMemoryDeliveredGateRouteStore::default()); + let route_store = Arc::new(in_memory_backed_outbound_state_store()); let services = make_services( coordinator, thread_service, @@ -1409,7 +1409,7 @@ mod tests { egress.allow_credential_handle("slack_bot_token"); let delivery_store = Arc::new(in_memory_backed_outbound_state_store()); - let route_store = Arc::new(InMemoryDeliveredGateRouteStore::default()); + let route_store = Arc::new(in_memory_backed_outbound_state_store()); let services = make_services( coordinator, thread_service, @@ -1526,7 +1526,7 @@ mod tests { let egress = Arc::new(FakeProtocolHttpEgress::new(vec!["slack.com".to_string()])); egress.allow_credential_handle("slack_bot_token"); let delivery_store = Arc::new(in_memory_backed_outbound_state_store()); - let route_store = Arc::new(InMemoryDeliveredGateRouteStore::default()); + let route_store = Arc::new(in_memory_backed_outbound_state_store()); let mut services = make_services( coordinator, thread_service, @@ -1610,7 +1610,7 @@ mod tests { ); let delivery_store = Arc::new(in_memory_backed_outbound_state_store()); - let route_store = Arc::new(InMemoryDeliveredGateRouteStore::default()); + let route_store = Arc::new(in_memory_backed_outbound_state_store()); let services = make_services( coordinator, thread_service, @@ -1692,7 +1692,7 @@ mod tests { egress.allow_credential_handle("slack_bot_token"); let delivery_store = Arc::new(in_memory_backed_outbound_state_store()); - let route_store = Arc::new(InMemoryDeliveredGateRouteStore::default()); + let route_store = Arc::new(in_memory_backed_outbound_state_store()); let services = make_services( coordinator, thread_service, @@ -1766,7 +1766,7 @@ mod tests { let driver = TriggeredRunDeliveryDriver::new( services, Arc::new(FailingOutcomeStore), - Arc::new(InMemoryDeliveredGateRouteStore::default()), + Arc::new(in_memory_backed_outbound_state_store()), scope.agent_id.clone().expect("test scope has agent"), ); let fire = minimal_trigger_fire(Some( @@ -1799,7 +1799,7 @@ mod tests { let outbound = Arc::new(in_memory_backed_outbound_state_store()); let egress = Arc::new(FakeProtocolHttpEgress::new(vec!["slack.com".to_string()])); let delivery_store = Arc::new(in_memory_backed_outbound_state_store()); - let route_store = Arc::new(InMemoryDeliveredGateRouteStore::default()); + let route_store = Arc::new(in_memory_backed_outbound_state_store()); let services = make_services(coordinator, thread_service, egress, outbound, install); let driver = TriggeredRunDeliveryDriver::new( @@ -1866,7 +1866,7 @@ mod tests { ); let delivery_store = Arc::new(in_memory_backed_outbound_state_store()); - let route_store = Arc::new(InMemoryDeliveredGateRouteStore::default()); + let route_store = Arc::new(in_memory_backed_outbound_state_store()); let services = make_services( coordinator, thread_service, @@ -1936,7 +1936,7 @@ mod tests { egress.allow_credential_handle("slack_bot_token"); let delivery_store = Arc::new(in_memory_backed_outbound_state_store()); - let route_store = Arc::new(InMemoryDeliveredGateRouteStore::default()); + let route_store = Arc::new(in_memory_backed_outbound_state_store()); let services = make_services( coordinator, thread_service, @@ -1996,7 +1996,7 @@ mod tests { egress.allow_credential_handle("slack_bot_token"); let delivery_store = Arc::new(in_memory_backed_outbound_state_store()); - let route_store = Arc::new(InMemoryDeliveredGateRouteStore::default()); + let route_store = Arc::new(in_memory_backed_outbound_state_store()); let services = make_services( coordinator, thread_service, @@ -2059,7 +2059,7 @@ mod tests { egress.allow_credential_handle("slack_bot_token"); let delivery_store = Arc::new(in_memory_backed_outbound_state_store()); - let route_store = Arc::new(InMemoryDeliveredGateRouteStore::default()); + let route_store = Arc::new(in_memory_backed_outbound_state_store()); let services = make_services(coordinator, thread_service, egress, outbound, install); let settings = FinalReplyDeliverySettings { poll_interval: std::time::Duration::ZERO, @@ -2138,7 +2138,7 @@ mod tests { thread_service, turn_coordinator: coordinator, outbound_store: outbound.clone(), - route_store: Arc::new(InMemoryDeliveredGateRouteStore::default()), + route_store: Arc::new(in_memory_backed_outbound_state_store()), communication_preferences: outbound, adapter: test_adapter(installation_id), egress, @@ -2580,7 +2580,7 @@ mod tests { thread_service: Arc::new(InMemorySessionThreadService::default()), turn_coordinator: coordinator, outbound_store: outbound.clone(), - route_store: Arc::new(InMemoryDeliveredGateRouteStore::default()), + route_store: Arc::new(in_memory_backed_outbound_state_store()), communication_preferences: outbound, adapter, egress: telegram_egress.clone(), @@ -3351,7 +3351,7 @@ mod tests { thread_service, turn_coordinator: coordinator, outbound_store: outbound.clone(), - route_store: Arc::new(InMemoryDeliveredGateRouteStore::default()), + route_store: Arc::new(in_memory_backed_outbound_state_store()), communication_preferences: outbound, adapter: test_adapter(install), egress: egress.clone(), @@ -3814,7 +3814,7 @@ mod tests { thread_service, turn_coordinator: coordinator, outbound_store: outbound.clone(), - route_store: Arc::new(InMemoryDeliveredGateRouteStore::default()), + route_store: Arc::new(in_memory_backed_outbound_state_store()), communication_preferences: outbound, adapter: test_adapter(install), egress: egress.clone(), @@ -3894,7 +3894,7 @@ mod tests { thread_service: Arc::new(InMemorySessionThreadService::default()), turn_coordinator: coordinator, outbound_store: outbound.clone(), - route_store: Arc::new(InMemoryDeliveredGateRouteStore::default()), + route_store: Arc::new(in_memory_backed_outbound_state_store()), communication_preferences: outbound, adapter: test_adapter(install), egress: egress.clone(), @@ -4174,7 +4174,7 @@ mod tests { thread_service, turn_coordinator: coordinator, outbound_store: outbound.clone(), - route_store: Arc::new(InMemoryDeliveredGateRouteStore::default()), + route_store: Arc::new(in_memory_backed_outbound_state_store()), communication_preferences: outbound, adapter: test_adapter(install), egress: egress.clone(), @@ -4399,7 +4399,7 @@ mod tests { ); let delivery_store = Arc::new(in_memory_backed_outbound_state_store()); - let route_store = Arc::new(InMemoryDeliveredGateRouteStore::default()); + let route_store = Arc::new(in_memory_backed_outbound_state_store()); let services = make_services( coordinator, thread_service, @@ -4537,7 +4537,7 @@ mod tests { ); let delivery_store = Arc::new(in_memory_backed_outbound_state_store()); - let route_store = Arc::new(InMemoryDeliveredGateRouteStore::default()); + let route_store = Arc::new(in_memory_backed_outbound_state_store()); let services = make_services( coordinator, thread_service, @@ -4647,7 +4647,7 @@ mod tests { ); let delivery_store = Arc::new(in_memory_backed_outbound_state_store()); - let route_store = Arc::new(InMemoryDeliveredGateRouteStore::default()); + let route_store = Arc::new(in_memory_backed_outbound_state_store()); let services = make_services( coordinator.clone(), Arc::new(InMemorySessionThreadService::default()), @@ -4745,7 +4745,7 @@ mod tests { let recorder = Arc::new(RecordingBlockedAuthFlowCanceller::default()); let delivery_store = Arc::new(in_memory_backed_outbound_state_store()); - let route_store = Arc::new(InMemoryDeliveredGateRouteStore::default()); + let route_store = Arc::new(in_memory_backed_outbound_state_store()); let services = make_services_with_canceller( coordinator.clone(), Arc::new(InMemorySessionThreadService::default()), @@ -4856,7 +4856,7 @@ mod tests { // returns `OAuthTargetNotDm` without posting.) let delivery_store = Arc::new(in_memory_backed_outbound_state_store()); - let route_store = Arc::new(InMemoryDeliveredGateRouteStore::default()); + let route_store = Arc::new(in_memory_backed_outbound_state_store()); let mut services = make_services( coordinator.clone(), Arc::new(InMemorySessionThreadService::default()), @@ -4964,7 +4964,7 @@ mod tests { let recorder = Arc::new(RecordingBlockedAuthFlowCanceller::default()); let delivery_store = Arc::new(in_memory_backed_outbound_state_store()); - let route_store = Arc::new(InMemoryDeliveredGateRouteStore::default()); + let route_store = Arc::new(in_memory_backed_outbound_state_store()); let mut services = make_services_with_canceller( coordinator.clone(), Arc::new(InMemorySessionThreadService::default()), @@ -5136,7 +5136,7 @@ mod tests { ); let delivery_store = Arc::new(in_memory_backed_outbound_state_store()); - let route_store = Arc::new(InMemoryDeliveredGateRouteStore::default()); + let route_store = Arc::new(in_memory_backed_outbound_state_store()); let mut services = make_services( coordinator, thread_service, @@ -5263,7 +5263,7 @@ mod tests { ); let delivery_store = Arc::new(in_memory_backed_outbound_state_store()); - let route_store = Arc::new(InMemoryDeliveredGateRouteStore::default()); + let route_store = Arc::new(in_memory_backed_outbound_state_store()); let mut services = make_services( coordinator, thread_service, @@ -5369,7 +5369,7 @@ mod tests { ); let delivery_store = Arc::new(in_memory_backed_outbound_state_store()); - let route_store = Arc::new(InMemoryDeliveredGateRouteStore::default()); + let route_store = Arc::new(in_memory_backed_outbound_state_store()); let mut services = make_services( coordinator, thread_service, @@ -5450,7 +5450,7 @@ mod tests { ExternalConversationRef::new(Some("T999"), "D789", Some("5555.6666"), None) .expect("envelope conv ref"); - let route_store = Arc::new(InMemoryDeliveredGateRouteStore::default()); + let route_store = Arc::new(in_memory_backed_outbound_state_store()); // Derive space_id from the envelope ref — mirrors the observer call site. let envelope_space_id = conversations_ref_from_product_ref(&envelope_conv_ref) @@ -5673,7 +5673,7 @@ mod tests { thread_service, turn_coordinator: coordinator, outbound_store: outbound.clone(), - route_store: Arc::new(InMemoryDeliveredGateRouteStore::default()), + route_store: Arc::new(in_memory_backed_outbound_state_store()), communication_preferences: outbound, adapter: test_adapter(install), egress: egress.clone(), @@ -5741,7 +5741,7 @@ mod tests { // ErroringTurnCoordinator: get_run_state always returns Err. turn_coordinator: Arc::new(ErroringTurnCoordinator), outbound_store: outbound.clone(), - route_store: Arc::new(InMemoryDeliveredGateRouteStore::default()), + route_store: Arc::new(in_memory_backed_outbound_state_store()), communication_preferences: outbound, adapter: test_adapter(install), egress: egress.clone(), @@ -6089,7 +6089,7 @@ mod tests { thread_service, turn_coordinator: coordinator.clone(), outbound_store: outbound.clone(), - route_store: Arc::new(InMemoryDeliveredGateRouteStore::default()), + route_store: Arc::new(in_memory_backed_outbound_state_store()), communication_preferences: outbound, adapter: test_adapter(install), egress: egress.clone(), @@ -6175,7 +6175,7 @@ mod tests { thread_service, turn_coordinator: coordinator.clone(), outbound_store: outbound.clone(), - route_store: Arc::new(InMemoryDeliveredGateRouteStore::default()), + route_store: Arc::new(in_memory_backed_outbound_state_store()), communication_preferences: outbound, adapter: test_adapter(install), egress: egress.clone(), @@ -6462,7 +6462,7 @@ mod tests { ); let delivery_store = Arc::new(in_memory_backed_outbound_state_store()); - let route_store = Arc::new(InMemoryDeliveredGateRouteStore::default()); + let route_store = Arc::new(in_memory_backed_outbound_state_store()); let mut services = make_services( coordinator, thread_service, @@ -6647,7 +6647,7 @@ mod tests { } let delivery_store = Arc::new(in_memory_backed_outbound_state_store()); - let route_store = Arc::new(InMemoryDeliveredGateRouteStore::default()); + let route_store = Arc::new(in_memory_backed_outbound_state_store()); let services = make_services( coordinator, thread_service, @@ -6747,7 +6747,7 @@ mod tests { } let delivery_store = Arc::new(in_memory_backed_outbound_state_store()); - let route_store = Arc::new(InMemoryDeliveredGateRouteStore::default()); + let route_store = Arc::new(in_memory_backed_outbound_state_store()); let services = make_services( coordinator, thread_service, @@ -6834,7 +6834,7 @@ mod tests { egress.allow_credential_handle("slack_bot_token"); let delivery_store = Arc::new(in_memory_backed_outbound_state_store()); - let route_store = Arc::new(InMemoryDeliveredGateRouteStore::default()); + let route_store = Arc::new(in_memory_backed_outbound_state_store()); let services = make_services( coordinator, thread_service, @@ -6929,7 +6929,7 @@ mod tests { } let delivery_store = Arc::new(in_memory_backed_outbound_state_store()); - let route_store = Arc::new(InMemoryDeliveredGateRouteStore::default()); + let route_store = Arc::new(in_memory_backed_outbound_state_store()); let mut services = make_services( coordinator, thread_service, diff --git a/crates/ironclaw_outbound/src/delivered_gate_routes.rs b/crates/ironclaw_outbound/src/delivered_gate_routes.rs index da77371e458..3525d499bc5 100644 --- a/crates/ironclaw_outbound/src/delivered_gate_routes.rs +++ b/crates/ironclaw_outbound/src/delivered_gate_routes.rs @@ -30,9 +30,6 @@ //! - Personal scope only: route records are only written for personal-scope //! triggers (the driver already fails closed to personal-only). -use std::collections::{BTreeSet, HashMap}; -use std::sync::Mutex; - use chrono::{DateTime, Duration, Utc}; use ironclaw_host_api::{TenantId, UserId}; use ironclaw_turns::{TurnRunId, TurnScope}; @@ -89,41 +86,6 @@ impl DeliveredGateRouteRecord { } } -/// Lookup key used by the routing wrapper. -#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] -struct RouteKey { - tenant_id: TenantId, - user_id: UserId, - gate_ref: String, -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -struct ConversationIndexKey { - tenant_id: TenantId, - user_id: UserId, - conversation_fingerprint: String, -} - -impl ConversationIndexKey { - fn new(tenant_id: TenantId, user_id: UserId, conversation_fingerprint: String) -> Self { - Self { - tenant_id, - user_id, - conversation_fingerprint, - } - } -} - -impl RouteKey { - fn new(tenant_id: TenantId, user_id: UserId, gate_ref: String) -> Self { - Self { - tenant_id, - user_id, - gate_ref, - } - } -} - /// Store for [`DeliveredGateRouteRecord`]s. /// /// Writes are best-effort: production callers must not propagate store errors @@ -190,207 +152,69 @@ pub trait DeliveredGateRouteStore: Send + Sync { ) -> Result; } -/// In-memory [`DeliveredGateRouteStore`]. +/// A no-op [`DeliveredGateRouteStore`] for surfaces that never route delivered +/// gates (e.g. the OpenAI-compatible API, which never produces approval/auth +/// resolution payloads, so the delivered-route fingerprint fallback is +/// unreachable there). /// -/// `conversation_index` is one-to-many: a single conversation fingerprint can -/// map to multiple route keys when a user has more than one pending gate -/// delivered to the same external conversation. `reverse_conversation_index` tracks which -/// conversation keys each route key owns so that removing one route touches -/// only its own entries in the shared conversation slot. -#[derive(Default)] -pub struct InMemoryDeliveredGateRouteStore { - state: Mutex, -} - -#[derive(Default)] -struct InMemoryDeliveredGateRouteState { - records: HashMap, - conversation_index: HashMap>, - reverse_conversation_index: HashMap>, -} +/// Reads return empty (miss → forward unchanged); best-effort cleanup +/// (`remove`/`sweep`) is a no-op. `record` fails **loud** — a surface that +/// actually records delivered routes must be wired with a real store +/// (a [`FilesystemOutboundStateStore`](crate::FilesystemOutboundStateStore)), +/// never this null object. This is the filesystem-free default +/// `ironclaw_product_workflow` uses when a caller supplies no routing store +/// (arch-simplification §4.3: replaces the deleted +/// `InMemoryDeliveredGateRouteStore`, a full HashMap store that was used merely +/// as an empty default on the non-routing surface). +#[derive(Debug, Default, Clone, Copy)] +pub struct NoopDeliveredGateRouteStore; #[async_trait::async_trait] -impl DeliveredGateRouteStore for InMemoryDeliveredGateRouteStore { +impl DeliveredGateRouteStore for NoopDeliveredGateRouteStore { async fn record_delivered_gate_route( &self, - record: DeliveredGateRouteRecord, + _record: DeliveredGateRouteRecord, ) -> Result<(), String> { - let key = RouteKey::new( - record.tenant_id.clone(), - record.user_id.clone(), - record.gate_ref.clone(), - ); - let conversation_keys = conversation_keys_for_record(&record); - let mut state = self - .state - .lock() - .unwrap_or_else(|poisoned| poisoned.into_inner()); - let InMemoryDeliveredGateRouteState { - records, - conversation_index, - reverse_conversation_index, - } = &mut *state; - - // Remove this route's old conversation index entries (idempotent - // re-record: replace old finger- prints without touching sibling - // routes that share any of the same conversation slots). - remove_key_from_conversation_indexes(&key, conversation_index, reverse_conversation_index); - - records.insert(key.clone(), record); - for conversation_key in conversation_keys { - conversation_index - .entry(conversation_key.clone()) - .or_default() - .insert(key.clone()); - reverse_conversation_index - .entry(key.clone()) - .or_default() - .push(conversation_key); - } - Ok(()) + Err( + "NoopDeliveredGateRouteStore cannot record delivered gate routes; \ + wire a real DeliveredGateRouteStore for surfaces that route gates" + .to_string(), + ) } async fn load_delivered_gate_route( &self, - tenant_id: &TenantId, - user_id: &UserId, - gate_ref: &str, + _tenant_id: &TenantId, + _user_id: &UserId, + _gate_ref: &str, ) -> Result, String> { - let key = RouteKey::new(tenant_id.clone(), user_id.clone(), gate_ref.to_string()); - Ok(self - .state - .lock() - .unwrap_or_else(|poisoned| poisoned.into_inner()) - .records - .get(&key) - .cloned()) + Ok(None) } async fn load_delivered_gate_route_by_conversation_fingerprint( &self, - tenant_id: &TenantId, - user_id: &UserId, - conversation_fingerprint: &str, + _tenant_id: &TenantId, + _user_id: &UserId, + _conversation_fingerprint: &str, ) -> Result, String> { - let conversation_key = ConversationIndexKey::new( - tenant_id.clone(), - user_id.clone(), - conversation_fingerprint.to_string(), - ); - let state = self - .state - .lock() - .unwrap_or_else(|poisoned| poisoned.into_inner()); - // Cap the number of route keys examined per conversation to guard - // against prompt-flood accumulation. Legitimate concurrent gate prompts - // in the same conversation are far fewer than this limit. - let result = state - .conversation_index - .get(&conversation_key) - .map(|route_keys| { - route_keys - .iter() - .take(DELIVERED_GATE_ROUTE_CONVERSATION_LOOKUP_CAP) - .filter_map(|rk| state.records.get(rk)) - .cloned() - .collect::>() - }) - .unwrap_or_default(); - Ok(result) + Ok(Vec::new()) } async fn remove_delivered_gate_route( &self, - tenant_id: &TenantId, - user_id: &UserId, - gate_ref: &str, + _tenant_id: &TenantId, + _user_id: &UserId, + _gate_ref: &str, ) -> Result<(), String> { - let key = RouteKey::new(tenant_id.clone(), user_id.clone(), gate_ref.to_string()); - let mut state = self - .state - .lock() - .unwrap_or_else(|poisoned| poisoned.into_inner()); - let InMemoryDeliveredGateRouteState { - records, - conversation_index, - reverse_conversation_index, - } = &mut *state; - - records.remove(&key); - remove_key_from_conversation_indexes(&key, conversation_index, reverse_conversation_index); Ok(()) } async fn sweep_expired_delivered_gate_routes( &self, - now: DateTime, + _now: DateTime, ) -> Result { - let mut state = self - .state - .lock() - .unwrap_or_else(|poisoned| poisoned.into_inner()); - let InMemoryDeliveredGateRouteState { - records, - conversation_index, - reverse_conversation_index, - } = &mut *state; - let before = records.len(); - let expired_keys: Vec = records - .iter() - .filter_map(|(key, record)| record.is_expired(now).then_some(key.clone())) - .collect(); - for key in &expired_keys { - records.remove(key); - remove_key_from_conversation_indexes( - key, - conversation_index, - reverse_conversation_index, - ); - } - Ok(before - records.len()) - } -} - -/// Remove all conversation-index entries owned by `key`. -/// -/// Removes `key` from `reverse_conversation_index`, then for each -/// conversation slot that `key` owned, removes `key` from the -/// `conversation_index` slot and drops the slot when it becomes empty. -/// This is the single authoritative implementation shared by -/// `record_delivered_gate_route` (idempotent re-record cleanup), -/// `remove_delivered_gate_route`, and `sweep_expired_delivered_gate_routes`. -fn remove_key_from_conversation_indexes( - key: &RouteKey, - conversation_index: &mut HashMap>, - reverse_conversation_index: &mut HashMap>, -) { - if let Some(old_conv_keys) = reverse_conversation_index.remove(key) { - for old_conv_key in old_conv_keys { - if let Some(slot) = conversation_index.get_mut(&old_conv_key) { - slot.remove(key); - if slot.is_empty() { - conversation_index.remove(&old_conv_key); - } - } - } - } -} - -fn conversation_keys_for_record(record: &DeliveredGateRouteRecord) -> Vec { - use std::collections::HashSet; - let mut seen: HashSet = HashSet::new(); - let mut keys: Vec = Vec::new(); - for conversation_fingerprint in &record.delivered_conversation_fingerprints { - let key = ConversationIndexKey::new( - record.tenant_id.clone(), - record.user_id.clone(), - conversation_fingerprint.clone(), - ); - if seen.insert(key.clone()) { - keys.push(key); - } + Ok(0) } - keys } #[cfg(test)] @@ -432,7 +256,7 @@ mod tests { #[tokio::test] async fn in_memory_store_round_trips_route_record() { - let store = InMemoryDeliveredGateRouteStore::default(); + let store = crate::test_support::in_memory_backed_outbound_state_store(); let rec = record("gate:round-trip-001"); store @@ -450,7 +274,7 @@ mod tests { #[tokio::test] async fn in_memory_store_returns_none_for_missing_key() { - let store = InMemoryDeliveredGateRouteStore::default(); + let store = crate::test_support::in_memory_backed_outbound_state_store(); let loaded = store .load_delivered_gate_route(&tenant(), &user(), "gate:does-not-exist") @@ -462,7 +286,7 @@ mod tests { #[tokio::test] async fn in_memory_store_key_includes_all_three_dimensions() { - let store = InMemoryDeliveredGateRouteStore::default(); + let store = crate::test_support::in_memory_backed_outbound_state_store(); // Same gate_ref, different user. let rec_a = DeliveredGateRouteRecord { @@ -537,7 +361,7 @@ mod tests { #[tokio::test] async fn in_memory_sweep_removes_only_expired_records() { - let store = InMemoryDeliveredGateRouteStore::default(); + let store = crate::test_support::in_memory_backed_outbound_state_store(); let now = Utc::now(); // Fresh record: recorded just now. @@ -584,7 +408,7 @@ mod tests { #[tokio::test] async fn in_memory_sweep_empty_store_returns_zero() { - let store = InMemoryDeliveredGateRouteStore::default(); + let store = crate::test_support::in_memory_backed_outbound_state_store(); let removed = store .sweep_expired_delivered_gate_routes(Utc::now()) .await @@ -594,7 +418,7 @@ mod tests { #[tokio::test] async fn in_memory_store_overwrites_on_second_write() { - let store = InMemoryDeliveredGateRouteStore::default(); + let store = crate::test_support::in_memory_backed_outbound_state_store(); let first = record("gate:overwrite-001"); let second = DeliveredGateRouteRecord { run_id: TurnRunId::new(), @@ -617,7 +441,7 @@ mod tests { #[tokio::test] async fn in_memory_conversation_lookup_round_trips() { - let store = InMemoryDeliveredGateRouteStore::default(); + let store = crate::test_support::in_memory_backed_outbound_state_store(); let conv_a = conversation_fingerprint("thread-conv-a"); let conv_b = conversation_fingerprint("thread-conv-b"); let rec = DeliveredGateRouteRecord { @@ -645,7 +469,7 @@ mod tests { #[tokio::test] async fn in_memory_conversation_lookup_returns_empty_for_unknown() { - let store = InMemoryDeliveredGateRouteStore::default(); + let store = crate::test_support::in_memory_backed_outbound_state_store(); let unknown = conversation_fingerprint("thread-unknown"); let loaded = store @@ -658,7 +482,7 @@ mod tests { #[tokio::test] async fn in_memory_ttl_expiry_removes_conversation_index() { - let store = InMemoryDeliveredGateRouteStore::default(); + let store = crate::test_support::in_memory_backed_outbound_state_store(); let now = Utc::now(); let conv = conversation_fingerprint("thread-expired-index"); let rec = DeliveredGateRouteRecord { @@ -702,7 +526,7 @@ mod tests { #[tokio::test] async fn in_memory_overwrite_updates_conversation_index() { - let store = InMemoryDeliveredGateRouteStore::default(); + let store = crate::test_support::in_memory_backed_outbound_state_store(); let conv_a = conversation_fingerprint("thread-overwrite-a"); let conv_b = conversation_fingerprint("thread-overwrite-b"); let first = DeliveredGateRouteRecord { @@ -736,7 +560,7 @@ mod tests { #[tokio::test] async fn in_memory_removing_old_route_preserves_reused_conversation_index() { - let store = InMemoryDeliveredGateRouteStore::default(); + let store = crate::test_support::in_memory_backed_outbound_state_store(); let shared = conversation_fingerprint("thread-reused-index"); let old = DeliveredGateRouteRecord { delivered_conversation_fingerprints: vec![shared.clone()], @@ -766,7 +590,7 @@ mod tests { #[tokio::test] async fn in_memory_two_routes_same_conversation_both_retrievable() { - let store = InMemoryDeliveredGateRouteStore::default(); + let store = crate::test_support::in_memory_backed_outbound_state_store(); let shared = conversation_fingerprint("thread-shared-two-routes"); let route_a = DeliveredGateRouteRecord { delivered_conversation_fingerprints: vec![shared.clone()], @@ -798,7 +622,7 @@ mod tests { #[tokio::test] async fn in_memory_removing_one_route_leaves_sibling() { - let store = InMemoryDeliveredGateRouteStore::default(); + let store = crate::test_support::in_memory_backed_outbound_state_store(); let shared = conversation_fingerprint("thread-sibling"); let route_a = DeliveredGateRouteRecord { delivered_conversation_fingerprints: vec![shared.clone()], @@ -831,7 +655,7 @@ mod tests { #[tokio::test] async fn in_memory_rerecording_one_route_does_not_disturb_sibling() { - let store = InMemoryDeliveredGateRouteStore::default(); + let store = crate::test_support::in_memory_backed_outbound_state_store(); let shared = conversation_fingerprint("thread-rerecord-sibling"); let route_a = DeliveredGateRouteRecord { delivered_conversation_fingerprints: vec![shared.clone()], @@ -882,7 +706,7 @@ mod tests { // Write 33 routes for the same conversation fingerprint. The lookup cap // (DELIVERED_GATE_ROUTE_CONVERSATION_LOOKUP_CAP = 32) must limit the returned set to at most 32 // records and must not panic. - let store = InMemoryDeliveredGateRouteStore::default(); + let store = crate::test_support::in_memory_backed_outbound_state_store(); let shared = conversation_fingerprint("thread-cap-33-entries"); for idx in 0..33u32 { diff --git a/crates/ironclaw_outbound/src/lib.rs b/crates/ironclaw_outbound/src/lib.rs index 1b1c0c9dea0..5b378fc9545 100644 --- a/crates/ironclaw_outbound/src/lib.rs +++ b/crates/ironclaw_outbound/src/lib.rs @@ -28,7 +28,7 @@ pub use communication_preferences::{ }; pub use delivered_gate_routes::{ DELIVERED_GATE_ROUTE_TTL, DeliveredGateRouteRecord, DeliveredGateRouteStore, - InMemoryDeliveredGateRouteStore, + NoopDeliveredGateRouteStore, }; pub use delivery_resolution::{ CommunicationDeliveryCandidate, CommunicationDeliveryIntent, CommunicationDeliveryKind, diff --git a/crates/ironclaw_product_workflow/src/workflow.rs b/crates/ironclaw_product_workflow/src/workflow.rs index daf1ee2235b..74e8b73571e 100644 --- a/crates/ironclaw_product_workflow/src/workflow.rs +++ b/crates/ironclaw_product_workflow/src/workflow.rs @@ -1,3 +1,4 @@ +// arch-exempt: large_file, §4.3 delete InMemoryDeliveredGateRouteStore (workflow default -> NoopDeliveredGateRouteStore; test doubles -> FilesystemOutboundStateStore helper), no logic change, plan #6168 //! Host-side `ProductWorkflow` implementation. //! //! This is the top-level product action orchestrator that dispatches inbound @@ -82,9 +83,7 @@ impl DefaultProductWorkflow { command_service: Arc::new(RejectingProductCommandService), approval_interaction_service: Arc::new(RejectingApprovalInteractionService), auth_interaction_service: Arc::new(RejectingAuthInteractionService), - delivered_gate_routes: Arc::new( - ironclaw_outbound::InMemoryDeliveredGateRouteStore::default(), - ), + delivered_gate_routes: Arc::new(ironclaw_outbound::NoopDeliveredGateRouteStore), } } diff --git a/crates/ironclaw_product_workflow/tests/product_workflow_contract.rs b/crates/ironclaw_product_workflow/tests/product_workflow_contract.rs index 4b645060132..0f81a721131 100644 --- a/crates/ironclaw_product_workflow/tests/product_workflow_contract.rs +++ b/crates/ironclaw_product_workflow/tests/product_workflow_contract.rs @@ -1,3 +1,4 @@ +// arch-exempt: large_file, §4.3 delete InMemoryDeliveredGateRouteStore (workflow default -> NoopDeliveredGateRouteStore; test doubles -> FilesystemOutboundStateStore helper), no logic change, plan #6168 //! Contract tests for the product workflow facade. use std::collections::HashMap; @@ -1560,7 +1561,7 @@ async fn scoped_approval_resolution_rejects_ambiguous_gate() { #[tokio::test] async fn scoped_approval_resolves_via_conversation_route() { let route_store: Arc = - Arc::new(ironclaw_outbound::InMemoryDeliveredGateRouteStore::default()); + Arc::new(ironclaw_outbound::test_support::in_memory_backed_outbound_state_store()); let (gate_ref, run_id, route_scope) = record_scoped_approval_conversation_route(route_store.as_ref(), Utc::now()).await; let approval_service = Arc::new(RecordingApprovalInteractionService::with_pending(Vec::new())); @@ -1600,7 +1601,7 @@ async fn scoped_approval_resolves_via_conversation_route() { #[tokio::test] async fn scoped_approval_misses_if_route_expired() { let route_store: Arc = - Arc::new(ironclaw_outbound::InMemoryDeliveredGateRouteStore::default()); + Arc::new(ironclaw_outbound::test_support::in_memory_backed_outbound_state_store()); record_scoped_approval_conversation_route( route_store.as_ref(), Utc::now() - ironclaw_outbound::DELIVERED_GATE_ROUTE_TTL - Duration::seconds(1), @@ -1629,7 +1630,7 @@ async fn scoped_approval_misses_if_route_expired() { #[tokio::test] async fn scoped_approval_misses_if_no_route() { let route_store: Arc = - Arc::new(ironclaw_outbound::InMemoryDeliveredGateRouteStore::default()); + Arc::new(ironclaw_outbound::test_support::in_memory_backed_outbound_state_store()); let approval_service = Arc::new(RecordingApprovalInteractionService::with_pending(Vec::new())); let workflow = DefaultProductWorkflow::new( Arc::new(FakeInboundTurnService::new()), @@ -1688,7 +1689,7 @@ async fn scoped_approval_missing_gate_fallback_reuses_dispatcher_binding() { // the base (topic-stripped) binding belongs to a different actor. Only a // fallback that reuses the dispatcher binding can resolve the gate. let route_store: Arc = - Arc::new(ironclaw_outbound::InMemoryDeliveredGateRouteStore::default()); + Arc::new(ironclaw_outbound::test_support::in_memory_backed_outbound_state_store()); let (gate_ref, run_id, _route_scope) = record_scoped_approval_conversation_route(route_store.as_ref(), Utc::now()).await; let binding_service = Arc::new(FakeConversationBindingService::new()); @@ -1757,7 +1758,7 @@ async fn scoped_approval_missing_gate_fallback_reuses_dispatcher_binding() { #[tokio::test] async fn auth_resolution_resolves_via_conversation_route_after_missing_auth() { let route_store: Arc = - Arc::new(ironclaw_outbound::InMemoryDeliveredGateRouteStore::default()); + Arc::new(ironclaw_outbound::test_support::in_memory_backed_outbound_state_store()); let gate_ref = GateRef::new("gate:auth-conversation-route").expect("auth gate ref"); let (run_id, route_scope) = record_conversation_route_for_gate_ref(route_store.as_ref(), gate_ref.as_str(), Utc::now()) @@ -1798,7 +1799,7 @@ async fn auth_resolution_resolves_via_conversation_route_after_missing_auth() { #[tokio::test] async fn explicit_approval_delivered_route_requires_gate_ref_match() { let route_store: Arc = - Arc::new(ironclaw_outbound::InMemoryDeliveredGateRouteStore::default()); + Arc::new(ironclaw_outbound::test_support::in_memory_backed_outbound_state_store()); let route_gate_ref = GateRef::new("gate:approval-route-match").expect("gate ref"); let payload_gate_ref = GateRef::new("gate:approval-route-mismatch").expect("gate ref"); record_conversation_route_for_gate_ref( @@ -1834,7 +1835,7 @@ async fn explicit_approval_delivered_route_requires_gate_ref_match() { #[tokio::test] async fn explicit_auth_delivered_route_requires_gate_ref_match() { let route_store: Arc = - Arc::new(ironclaw_outbound::InMemoryDeliveredGateRouteStore::default()); + Arc::new(ironclaw_outbound::test_support::in_memory_backed_outbound_state_store()); let route_gate_ref = GateRef::new("gate:auth-route-match").expect("gate ref"); let payload_gate_ref = GateRef::new("gate:auth-route-mismatch").expect("gate ref"); record_conversation_route_for_gate_ref( @@ -2043,7 +2044,7 @@ async fn scoped_approval_one_stale_one_pending_resolves_and_prunes() { #[tokio::test] async fn scoped_approval_one_expired_one_live_resolves_live() { let route_store: Arc = - Arc::new(ironclaw_outbound::InMemoryDeliveredGateRouteStore::default()); + Arc::new(ironclaw_outbound::test_support::in_memory_backed_outbound_state_store()); // Record an expired route first. record_scoped_approval_conversation_route( route_store.as_ref(), @@ -2092,7 +2093,7 @@ async fn scoped_approval_one_expired_one_live_resolves_live() { #[tokio::test] async fn scoped_approval_actor_mismatch_filtered_out() { let route_store: Arc = - Arc::new(ironclaw_outbound::InMemoryDeliveredGateRouteStore::default()); + Arc::new(ironclaw_outbound::test_support::in_memory_backed_outbound_state_store()); // Record a route owned by user:user2 — different actor than the envelope's user1. let tenant_id = TenantId::new("tenant:install_alpha").expect("tenant"); let other_user_id = UserId::new("user:user2").expect("other user"); @@ -2155,7 +2156,7 @@ async fn scoped_approval_actor_mismatch_filtered_out() { #[tokio::test] async fn explicit_approval_gate_ref_mismatch_leaves_original_rejection() { let route_store: Arc = - Arc::new(ironclaw_outbound::InMemoryDeliveredGateRouteStore::default()); + Arc::new(ironclaw_outbound::test_support::in_memory_backed_outbound_state_store()); let route_gate_ref = GateRef::new("gate:approval-stored-ref").expect("stored gate ref"); let payload_gate_ref = GateRef::new("gate:approval-payload-ref").expect("payload gate ref"); record_conversation_route_for_gate_ref( @@ -2315,7 +2316,7 @@ async fn auth_two_live_routes_same_conversation_rejects_ambiguous() { #[tokio::test] async fn bare_auth_deny_with_stale_approval_route_selects_auth_route_not_approval() { let route_store: Arc = - Arc::new(ironclaw_outbound::InMemoryDeliveredGateRouteStore::default()); + Arc::new(ironclaw_outbound::test_support::in_memory_backed_outbound_state_store()); // Store a live APPROVAL-prefixed route in the same conversation bucket. let stale_approval_gate = approval_gate_ref(ApprovalRequestId::new()).expect("approval gate ref"); @@ -2472,7 +2473,7 @@ impl ApprovalInteractionService for StaleGateReturningApprovalService { #[tokio::test] async fn auth_resolution_stale_auth_does_not_fall_back_to_delivered_route() { let route_store: Arc = - Arc::new(ironclaw_outbound::InMemoryDeliveredGateRouteStore::default()); + Arc::new(ironclaw_outbound::test_support::in_memory_backed_outbound_state_store()); let gate_ref = GateRef::new("gate:auth-stale-no-fallback").expect("auth gate ref"); // Record a live delivered route for the same gate so that IF the fallback ran // it would resolve successfully — confirming the test would catch a regression. @@ -2527,7 +2528,7 @@ async fn auth_resolution_stale_auth_does_not_fall_back_to_delivered_route() { #[tokio::test] async fn explicit_approval_stale_gate_surfaces_without_fallback() { let route_store: Arc = - Arc::new(ironclaw_outbound::InMemoryDeliveredGateRouteStore::default()); + Arc::new(ironclaw_outbound::test_support::in_memory_backed_outbound_state_store()); let gate_ref = approval_gate_ref(ApprovalRequestId::new()).expect("approval gate ref"); // Record a live delivered route for the same gate so that IF the bare-skip // fallback ran it would find it — confirming the test would catch a regression. @@ -2612,7 +2613,7 @@ async fn exact_named_generic_approval_gate_is_forwarded_not_dropped_by_kind_filt // the old code would have dropped this route; the fixed code must not. let generic_gate_ref = "gate:approve-slack"; let route_store: Arc = - Arc::new(ironclaw_outbound::InMemoryDeliveredGateRouteStore::default()); + Arc::new(ironclaw_outbound::test_support::in_memory_backed_outbound_state_store()); let (run_id, route_scope) = record_conversation_route_for_gate_ref(route_store.as_ref(), generic_gate_ref, Utc::now()) .await; @@ -2687,7 +2688,7 @@ async fn exact_named_generic_approval_gate_is_forwarded_not_dropped_by_kind_filt #[tokio::test] async fn explicit_auth_gate_ref_mismatch_leaves_original_rejection() { let route_store: Arc = - Arc::new(ironclaw_outbound::InMemoryDeliveredGateRouteStore::default()); + Arc::new(ironclaw_outbound::test_support::in_memory_backed_outbound_state_store()); let route_gate_ref = GateRef::new("gate:auth-stored-ref").expect("stored gate ref"); let payload_gate_ref = GateRef::new("gate:auth-payload-ref").expect("payload gate ref"); record_conversation_route_for_gate_ref( @@ -2760,7 +2761,7 @@ async fn bare_approve_with_invalid_stored_approval_route_rejects_invalid_gate_re ); let route_store: Arc = - Arc::new(ironclaw_outbound::InMemoryDeliveredGateRouteStore::default()); + Arc::new(ironclaw_outbound::test_support::in_memory_backed_outbound_state_store()); record_conversation_route_for_gate_ref(route_store.as_ref(), &invalid_gate_ref_str, Utc::now()) .await; @@ -2839,7 +2840,7 @@ async fn bare_auth_deny_with_invalid_stored_auth_route_rejects_invalid_gate_ref( ); let route_store: Arc = - Arc::new(ironclaw_outbound::InMemoryDeliveredGateRouteStore::default()); + Arc::new(ironclaw_outbound::test_support::in_memory_backed_outbound_state_store()); record_conversation_route_for_gate_ref(route_store.as_ref(), &invalid_gate_ref_str, Utc::now()) .await; diff --git a/crates/ironclaw_reborn_composition/src/delivered_gate_routing.rs b/crates/ironclaw_reborn_composition/src/delivered_gate_routing.rs index ad027701494..2ea70a50d07 100644 --- a/crates/ironclaw_reborn_composition/src/delivered_gate_routing.rs +++ b/crates/ironclaw_reborn_composition/src/delivered_gate_routing.rs @@ -190,7 +190,8 @@ mod tests { use async_trait::async_trait; use ironclaw_host_api::{AgentId, TenantId, ThreadId, UserId}; - use ironclaw_outbound::{DeliveredGateRouteRecord, InMemoryDeliveredGateRouteStore}; + use ironclaw_outbound::DeliveredGateRouteRecord; + use ironclaw_outbound::test_support::in_memory_backed_outbound_state_store; use ironclaw_product_workflow::{ ApprovalInteractionDecision, ListPendingApprovalsRequest, ListPendingApprovalsResponse, ProductWorkflowError, ResolveApprovalInteractionRequest, @@ -301,7 +302,7 @@ mod tests { delivered_conversation_fingerprints: Vec::new(), }; - let route_store = Arc::new(InMemoryDeliveredGateRouteStore::default()); + let route_store = Arc::new(in_memory_backed_outbound_state_store()); route_store .record_delivered_gate_route(route_record) .await @@ -332,7 +333,7 @@ mod tests { #[tokio::test] async fn miss_forwards_request_unchanged() { - let route_store = Arc::new(InMemoryDeliveredGateRouteStore::default()); + let route_store = Arc::new(in_memory_backed_outbound_state_store()); // No record stored — simulates a normal same-thread live run. let inner = Arc::new(RecordingApprovalService::default()); @@ -372,7 +373,7 @@ mod tests { delivered_conversation_fingerprints: Vec::new(), }; - let route_store = Arc::new(InMemoryDeliveredGateRouteStore::default()); + let route_store = Arc::new(in_memory_backed_outbound_state_store()); // The lookup key encodes the other user — the requesting user won't // find this record at all (different key). This tests the user_id // guard when the key happens to be present but for a different user. @@ -414,7 +415,7 @@ mod tests { #[tokio::test] async fn list_pending_forwards_unchanged() { - let route_store = Arc::new(InMemoryDeliveredGateRouteStore::default()); + let route_store = Arc::new(in_memory_backed_outbound_state_store()); let inner = Arc::new(RecordingApprovalService::default()); let service = DeliveredGateRoutingApprovalService::new(Arc::clone(&inner) as _, route_store); @@ -564,7 +565,7 @@ mod tests { run_thread.clone(), Some(route_user.clone()), ); - let route_store = Arc::new(InMemoryDeliveredGateRouteStore::default()); + let route_store = Arc::new(in_memory_backed_outbound_state_store()); route_store .record_delivered_gate_route(DeliveredGateRouteRecord { tenant_id: route_tenant.clone(), @@ -758,7 +759,7 @@ mod tests { delivered_conversation_fingerprints: Vec::new(), }; - let route_store = Arc::new(InMemoryDeliveredGateRouteStore::default()); + let route_store = Arc::new(in_memory_backed_outbound_state_store()); route_store .record_delivered_gate_route(route_record) .await diff --git a/crates/ironclaw_reborn_composition/src/slack/slack_serve/e2e_tests.rs b/crates/ironclaw_reborn_composition/src/slack/slack_serve/e2e_tests.rs index dde757171f3..b4b15b02d1f 100644 --- a/crates/ironclaw_reborn_composition/src/slack/slack_serve/e2e_tests.rs +++ b/crates/ironclaw_reborn_composition/src/slack/slack_serve/e2e_tests.rs @@ -289,7 +289,7 @@ async fn build_harness_with_full_settings( )); let auths = Arc::new(RecordingAuthInteractionService::new(coordinator.clone())); let route_store: Arc = - Arc::new(ironclaw_outbound::InMemoryDeliveredGateRouteStore::default()); + Arc::new(ironclaw_outbound::test_support::in_memory_backed_outbound_state_store()); let inbound = Arc::new(DefaultInboundTurnService::new( binding.clone(), @@ -426,7 +426,8 @@ impl ApprovalInteractionService for ForeignScopeApprovalService { /// fallback path. Returns the harness (with `route_store` accessible) and the /// underlying recording approval service for request assertions. /// -/// By default, two separate `InMemoryDeliveredGateRouteStore` instances are used: +/// By default, two separate in-memory-backed `FilesystemOutboundStateStore` +/// instances serve the route-store role: /// /// - `workflow_route_store` (exposed via `harness.route_store`): the store the /// workflow queries when resolving delivered-gate-route fallback paths. Tests @@ -497,7 +498,7 @@ async fn build_harness_for_delivered_route_tests_with_store_mode( // workflow_route_store: queried by the workflow during delivered-route fallback. // Tests seed records here to control the outcome (Miss / Single / Ambiguous). let workflow_route_store: Arc = - Arc::new(ironclaw_outbound::InMemoryDeliveredGateRouteStore::default()); + Arc::new(ironclaw_outbound::test_support::in_memory_backed_outbound_state_store()); // observer_route_store: written by the delivery observer when it auto-records a // gate route after posting an approval prompt. Kept separate so auto-created // routes never bleed into the workflow's index. @@ -505,7 +506,7 @@ async fn build_harness_for_delivered_route_tests_with_store_mode( if share_observer_and_workflow_route_store { workflow_route_store.clone() } else { - Arc::new(ironclaw_outbound::InMemoryDeliveredGateRouteStore::default()) + Arc::new(ironclaw_outbound::test_support::in_memory_backed_outbound_state_store()) }; let inbound = Arc::new(DefaultInboundTurnService::new( @@ -1319,7 +1320,7 @@ async fn triggered_auth_prompt_route_delivers_dm_setup_link_on_foreign_scope() { let outbound_store: Arc = outbound.clone(); let preferences: Arc = outbound; let route_store: Arc = - Arc::new(ironclaw_outbound::InMemoryDeliveredGateRouteStore::default()); + Arc::new(ironclaw_outbound::test_support::in_memory_backed_outbound_state_store()); let services = FinalReplyDeliveryServices { channel_protocol: Arc::new(crate::slack::slack_delivery::SlackDeliveryProtocol), binding_service: Arc::new(NoopTriggeredBindingService), @@ -1456,7 +1457,7 @@ async fn triggered_auth_prompt_oauth_target_not_dm_suppresses_setup_link_and_can let outbound_store: Arc = outbound.clone(); let preferences: Arc = outbound; let route_store: Arc = - Arc::new(ironclaw_outbound::InMemoryDeliveredGateRouteStore::default()); + Arc::new(ironclaw_outbound::test_support::in_memory_backed_outbound_state_store()); let services = FinalReplyDeliveryServices { channel_protocol: Arc::new(crate::slack::slack_delivery::SlackDeliveryProtocol), binding_service: Arc::new(NoopTriggeredBindingService), @@ -3305,7 +3306,7 @@ async fn build_harness_for_auth_fanout_test( )); let auths = Arc::new(RecordingAuthInteractionService::new(coordinator.clone())); let route_store: Arc = - Arc::new(ironclaw_outbound::InMemoryDeliveredGateRouteStore::default()); + Arc::new(ironclaw_outbound::test_support::in_memory_backed_outbound_state_store()); let inbound = Arc::new(DefaultInboundTurnService::new( binding.clone(),