{"record":{"id":"f3fcfeaca4e3e5ab","repo":"zeroclaw-labs/zeroclaw","slug":"since-must-be-before-until-f3fcfe","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/markdown.rs","lineNumber":237,"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 all = self.read_all_entries().await?;\n        let keywords: Vec<String> = if is_recent_recall_query(query) {\n            Vec::new()\n        } else {\n            query\n                .to_lowercase()\n                .split_whitespace()\n                .map(str::to_string)\n                .collect()\n        };\n\n        let mut scored: Vec<MemoryEntry> = all\n            .into_iter()\n            .filter_map(|mut entry| {\n                if !entry_in_window(&entry.timestamp, since_dt.as_ref(), until_dt.as_ref()) {\n                    return None;","sourceCodeStart":219,"sourceCodeEnd":255,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-memory/src/markdown.rs#L219-L255","documentation":"The same since < until contract enforced on the markdown backend's recall: both bounds are parsed as RFC 3339 and the call fails when since >= until (equal instants included). It fires before any entries are read, so it costs nothing and protects the daily-file scan from inverted windows.","triggerScenarios":"Calling MarkdownMemory::recall (directly or via recall_for_agents) with since on or after until — same shapes as the lucid variant: swapped args, equal instants, offset-induced inversion.","commonSituations":"Date pickers allowing zero-length ranges; string-sorted date pairs passed unsorted; DST shifts flipping the order of same-local-time bounds; migration tooling passing through user-supplied dates unvalidated.","solutions":["Swap the bounds so since is strictly before until.","Widen until when a single-instant window was intended.","Validate and normalize both bounds (UTC) in the caller before invoking recall."],"exampleFix":"// before\nlet entries = markdown.recall(query, limit, None,\n    Some(\"2026-08-22T00:00:00Z\"),\n    Some(\"2026-08-01T00:00:00Z\"),\n).await?;\n\n// after\nlet entries = markdown.recall(query, limit, None,\n    Some(\"2026-08-01T00:00:00Z\"),\n    Some(\"2026-08-22T00:00:00Z\"),\n).await?;","handlingStrategy":"validation","validationCode":"fn valid_recall_window(since: Option<&str>, until: Option<&str>) -> anyhow::Result<()> {\n    if let (Some(s), Some(u)) = (since, until)\n        && let (Ok(s), Ok(u)) = (\n            chrono::DateTime::parse_from_rfc3339(s),\n            chrono::DateTime::parse_from_rfc3339(u),\n        )\n        && s >= u\n    {\n        anyhow::bail!(\"since ({s}) must be strictly before until ({u})\");\n    }\n    Ok(())\n}","typeGuard":"fn is_valid_recall_window(since: Option<&str>, until: Option<&str>) -> bool {\n    valid_recall_window(since, until).is_ok()\n}","tryCatchPattern":null,"preventionTips":["Validate date ranges in UI layers before submission; date pickers should not allow a zero-length window.","Log the parsed window bounds (both UTC instants) at the call site so inversions are obvious in traces."],"tags":["validation","date-range","rfc3339","recall","markdown"],"backgroundTag":"invalid-date-range","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}