{"record":{"id":"fd4310193a15cb19","repo":"unicity-aos/aos-ce","slug":"hook-adapter-oracle-incomplete-context-fan-out-on-reply","errorCode":null,"errorMessage":"hook-adapter-oracle: incomplete context fan-out on {reply_topic}; dropping all partial context","messagePattern":"hook-adapter-oracle: incomplete context fan-out on (.+?); dropping all partial context","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"capsules/capsule-hook-adapter-oracle/src/lib.rs","lineNumber":271,"sourceCode":"    let mut contexts = Vec::new();\n    let mut context_bytes = 0;\n    let start = time::monotonic();\n    loop {\n        let elapsed_ms = u64::try_from((time::monotonic().saturating_sub(start)).as_millis())\n            .unwrap_or(HOST_HOOK_COLLECT_DEADLINE_MS);\n        if elapsed_ms >= HOST_HOOK_COLLECT_DEADLINE_MS {\n            break;\n        }\n        let remaining = if contexts.is_empty() {\n            HOST_HOOK_COLLECT_DEADLINE_MS - elapsed_ms\n        } else {\n            HOOK_QUIESCENCE_MS.min(HOST_HOOK_COLLECT_DEADLINE_MS - elapsed_ms)\n        };\n        match subscription.recv(remaining) {\n            Ok(poll) if poll.messages.is_empty() => break,\n            Ok(poll) => {\n                if poll.dropped != 0 || poll.lagged != 0 {\n                    log::warn(format!(\n                        \"hook-adapter-oracle: incomplete context fan-out on {reply_topic}; dropping all partial context\"\n                    ));\n                    return Ok(None);\n                }\n                for message in poll.messages {\n                    if message.topic != reply_topic\n                        || message.principal.verified() != Some(principal)\n                    {\n                        log::warn(format!(\n                            \"hook-adapter-oracle: dropping mismatched context reply on {reply_topic}\"\n                        ));\n                        continue;\n                    }\n                    match serde_json::from_str::<serde_json::Value>(&message.payload) {\n                        Ok(value) => {\n                            if let Some(context) = value\n                                .get(\"additional_context\")\n                                .and_then(serde_json::Value::as_str)","sourceCodeStart":253,"sourceCodeEnd":289,"githubUrl":"https://github.com/unicity-aos/aos-ce/blob/f6f22024fb1e8d122f28a1b4a9f75aee448ae839/capsules/capsule-hook-adapter-oracle/src/lib.rs#L253-L289","documentation":"collect_additional_context fans out a context request on a reply topic and collects responses until a deadline. If the poll reports dropped != 0 or lagged != 0, the fan-out was incomplete — some context replies were lost — so the adapter drops ALL partial context (returns Ok(None)) rather than act on incomplete data. The warning documents why the context is being discarded.","triggerScenarios":"dispatch_oracle_hook -> collect_additional_context receives a subscription poll where poll.dropped or poll.lagged is non-zero, meaning the reply-topic buffer overflowed or the subscriber lagged behind during the quiescence/deadline window.","commonSituations":"Many hooks replying concurrently to the same reply topic faster than the adapter drains them; HOOK_QUIESCENCE_MS/deadline window too short for the reply volume; host under heavy load starving the subscriber.","solutions":["Increase the reply-topic subscription buffer capacity so fan-out bursts fit.","Raise the collect deadline (HOST_HOOK_COLLECT_DEADLINE_MS) or quiescence window to give slow replies time to arrive.","Retry the context request once on a fresh reply topic when drops are detected.","Reduce concurrent hook fan-out (batch or serialize context requests) to keep reply volume within buffer limits."],"exampleFix":"// before: tight deadline causes drops under load\nlet remaining = HOOK_QUIESCENCE_MS.min(HOST_HOOK_COLLECT_DEADLINE_MS - elapsed_ms);\n// after: larger deadline and one retry on incomplete fan-out\nlet remaining = HOOK_QUIESCENCE_MS.min(HOST_HOOK_COLLECT_DEADLINE_MS_MAX - elapsed_ms);\nmatch collect(replies, remaining) {\n    Err(IncompleteFanOut) => collect(replies, remaining * 2),\n    other => other,\n}","handlingStrategy":"fallback","validationCode":"// treat dropped/lagged polls as incomplete and plan a retry\nif poll.dropped != 0 || poll.lagged != 0 { request_context_again_with_fresh_topic(); }","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Size reply-topic buffers for expected fan-out fan-in bursts","Set collect deadlines generously relative to hook latency","Retry the context request on a fresh reply topic after any drop"],"tags":["ipc","event-bus","message-loss","hooks","rust"],"backgroundTag":"message-loss-detected","analyzedSha":"f6f22024fb1e8d122f28a1b4a9f75aee448ae839","analyzedAt":"2026-09-13T03:04:44.565Z","contentChangedAt":"2026-09-13T03:04:44.565Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}