{"record":{"id":"91d01cc8fa3fcfa6","repo":"zeroclaw-labs/zeroclaw","slug":"amqp-dispatch-routes-to-the-sop-engine","errorCode":null,"errorMessage":"amqp.{}: dispatch = {:?} routes to the SOP engine but no SOP engine/audit handles are available; refusing to start a channel that would acknowledge deliveries without dispatching them","messagePattern":"amqp\\.(.+?): dispatch = (.+?) routes to the SOP engine but no SOP engine/audit handles are available; refusing to start a channel that would acknowledge deliveries without dispatching them","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"critical","filePath":"crates/zeroclaw-channels/src/amqp.rs","lineNumber":87,"sourceCode":"    /// in SOP-only dispatch mode. The delivery was NOT fully handled: under\n    /// `durable_ack` it must be nack/requeued for redelivery rather than acked,\n    /// so the trigger is retried once capacity frees instead of being lost. Never\n    /// returned for combined `sop_and_agent_loop` dispatch - there, the agent loop\n    /// already consumed the delivery, so a SOP-side overflow is surfaced loudly\n    /// and acked rather than risking a broker redelivery that would double-run\n    /// the agent side (see `route_delivery`).\n    Deferred,\n    ReceiverGone,\n}\n\nimpl AmqpChannel {\n    pub fn new(cfg: AmqpChannelConfig) -> anyhow::Result<Self> {\n        let routes_sop = matches!(\n            cfg.dispatch,\n            SopDispatch::Sop | SopDispatch::SopAndAgentLoop\n        );\n        if routes_sop && (cfg.engine.is_none() || cfg.audit.is_none()) {\n            anyhow::bail!(\n                \"amqp.{}: dispatch = {:?} routes to the SOP engine but no SOP \\\n                 engine/audit handles are available; refusing to start a \\\n                 channel that would acknowledge deliveries without dispatching \\\n                 them\",\n                cfg.alias,\n                cfg.dispatch\n            );\n        }\n        Ok(Self {\n            amqp_url: cfg.amqp_url,\n            exchange: cfg.exchange,\n            routing_keys: cfg.routing_keys,\n            queue: cfg.queue,\n            ca_cert: cfg.ca_cert,\n            client_cert: cfg.client_cert,\n            client_key: cfg.client_key,\n            sender_label: cfg.sender_label,\n            content_template: cfg.content_template,","sourceCodeStart":69,"sourceCodeEnd":105,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/amqp.rs#L69-L105","documentation":"AmqpChannel::new refuses to construct when dispatch is Sop or SopAndAgentLoop but the SOP engine and/or audit logger handles are absent. Without them the consumer loop would acknowledge broker deliveries that were never dispatched anywhere — silent message loss — so the constructor fails closed at startup instead.","triggerScenarios":"Channel configuration sets amqp dispatch to sop (or sop_and_agent_loop) while the SOP subsystem is disabled or unwired, so AmqpChannelConfig.engine and/or .audit are None when AmqpChannel::new runs.","commonSituations":"Enabling SOP routing in config while the SOP engine is disabled globally; refactors that stop threading engine/audit handles into channel construction; test or staging wiring that omits the handles.","solutions":["Provide both handles in AmqpChannelConfig: wire the SopEngine (Arc<Mutex<SopEngine>>) and SopAuditLogger (Arc<SopAuditLogger>) through from the runtime.","Or switch the channel's dispatch to agent_loop if SOP routing is not actually needed.","Check the config surface that builds AmqpChannelConfig — SOP enablement is usually global, so verify the engine is on at all before setting per-channel dispatch."],"exampleFix":"// before\nlet ch = AmqpChannel::new(AmqpChannelConfig {\n    dispatch: SopDispatch::Sop,\n    engine: None,\n    audit: None,\n    // ...\n})?;\n\n// after\nlet ch = AmqpChannel::new(AmqpChannelConfig {\n    dispatch: SopDispatch::Sop,\n    engine: Some(engine_handle),\n    audit: Some(audit_handle),\n    // ...\n})?;","handlingStrategy":"validation","validationCode":"let routes_sop = matches!(cfg.dispatch, SopDispatch::Sop | SopDispatch::SopAndAgentLoop);\nif routes_sop && (cfg.engine.is_none() || cfg.audit.is_none()) {\n    anyhow::bail!(\n        \"config error: amqp.{} needs SOP engine+audit handles or dispatch = agent_loop\",\n        cfg.alias\n    );\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Fail channel construction early on inconsistent config — never ack-and-drop deliveries","Keep dispatch mode and SOP enablement in one config surface so they cannot diverge","Add a startup config lint for amqp channels that route to SOP without handles"],"tags":["rust","amqp","sop","config-validation","fail-closed","message-loss-prevention"],"backgroundTag":"config-validation-failed","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}