{"record":{"id":"894aef3c59ced5b5","repo":"zeroclaw-labs/zeroclaw","slug":"since-must-be-before-until","errorCode":null,"errorMessage":"'since' must be before 'until'","messagePattern":"'since' must be before 'until'","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-memory/src/lucid.rs","lineNumber":448,"sourceCode":"        let until_dt = until\n            .map(chrono::DateTime::parse_from_rfc3339)\n            .transpose()\n            .map_err(|e| {\n                ::zeroclaw_log::record!(\n                    WARN,\n                    ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Reject)\n                        .with_outcome(::zeroclaw_log::EventOutcome::Failure)\n                        .with_attrs(\n                            ::serde_json::json!({\"field\": \"until\", \"error\": format!(\"{}\", e)})\n                        ),\n                    \"recall window bound rejected\"\n                );\n                anyhow::Error::msg(format!(\"invalid 'until' date (expected RFC 3339): {e}\"))\n            })?;\n        if let (Some(s), Some(u)) = (&since_dt, &until_dt)\n            && s >= u\n        {\n            anyhow::bail!(\"'since' must be before 'until'\");\n        }\n\n        let recall_query = normalize_recent_recall_query(query);\n\n        let local_results = self\n            .local\n            .recall(recall_query, limit, session_id, since, until)\n            .await?;\n        if limit == 0\n            || local_results.len() >= limit\n            || local_results.len() >= self.local_hit_threshold\n        {\n            return Ok(local_results);\n        }\n\n        if self.in_failure_cooldown() {\n            return Ok(local_results);\n        }","sourceCodeStart":430,"sourceCodeEnd":466,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-memory/src/lucid.rs#L430-L466","documentation":"LucidMemory::recall parses the optional since/until RFC 3339 window filters and requires since < until strictly — the check uses s >= u, so equal instants are rejected too. It fires before the local sqlite lookup and any lucid subprocess, purely from argument validation, and exists so an inverted range never silently returns zero results.","triggerScenarios":"Calling recall (or a higher path reaching it) with both filters where since is on or after until, e.g. since=\"2026-01-10T00:00:00Z\", until=\"2026-01-02T00:00:00Z\", or both set to the same instant.","commonSituations":"Swapped since/until arguments; inclusive single-instant ranges where both ends are equal; timezone offsets (+02:00 vs Z) making the since instant land after until; UI date pickers sending default zero or identical dates.","solutions":["Order the arguments so since is strictly earlier than until.","If the intent is a single instant, widen until by at least one second (or one unit of your choice).","Normalize both timestamps to UTC before sending so offsets cannot invert the comparison."],"exampleFix":"// before\nmemory.recall(query, limit, session_id,\n    Some(\"2026-01-10T00:00:00Z\"),  // since (later)\n    Some(\"2026-01-02T00:00:00Z\"),  // until (earlier)\n).await?;\n\n// after\nmemory.recall(query, limit, session_id,\n    Some(\"2026-01-02T00:00:00Z\"),  // since (earlier)\n    Some(\"2026-01-10T00:00:00Z\"),  // until (later)\n).await?;","handlingStrategy":"validation","validationCode":"fn valid_recall_window(since: Option<&str>, until: Option<&str>) -> bool {\n    match (since, until) {\n        (Some(s), Some(u)) => match (\n            chrono::DateTime::parse_from_rfc3339(s),\n            chrono::DateTime::parse_from_rfc3339(u),\n        ) {\n            (Ok(s), Ok(u)) => s < u, // strict: equal instants are rejected\n            _ => false,\n        },\n        _ => true,\n    }\n}","typeGuard":"fn is_valid_recall_window(since: Option<&str>, until: Option<&str>) -> bool {\n    valid_recall_window(since, until)\n}","tryCatchPattern":"if !valid_recall_window(since, until) {\n    return Ok(Vec::new()); // or surface a 4xx to the caller\n}\nmemory.recall(query, limit, session_id, since, until).await;","preventionTips":["Validate the window at the API boundary (before it reaches memory) and reject inverted ranges with a client-facing message.","Render since/until from typed DateTime values serialized to UTC RFC 3339 instead of passing through user strings.","Treat equal since/until as invalid in callers — the backend enforces strict inequality."],"tags":["validation","date-range","rfc3339","recall","lucid"],"backgroundTag":"invalid-date-range","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}