{"record":{"id":"eb847e6c336cfbf5","repo":"zeroclaw-labs/zeroclaw","slug":"memory-fact-write-denied-by-policy-e","errorCode":null,"errorMessage":"memory fact write denied by policy: {e}","messagePattern":"memory fact write denied by policy: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-memory/src/consolidation.rs","lineNumber":316,"sourceCode":"                fact_overlaps_primary_update(trimmed, primary, memory_config)\n            });\n            (!trimmed.is_empty() && !overlaps_primary).then_some(trimmed)\n        })\n        .take(MAX_TYPED_FACTS_PER_TURN)\n    {\n        // Same fail-closed policy write-gate as the primary core update; each\n        // fact is an autonomous Core write.\n        if let Err(e) =\n            policy_gate::validate_store(memory, &policy, \"default\", &MemoryCategory::Core).await\n        {\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(::serde_json::json!({\"error\": e.to_string()})),\n                \"memory fact write denied by policy\"\n            );\n            anyhow::bail!(\"memory fact write denied by policy: {e}\");\n        }\n\n        let mem_key = format!(\"core_fact_{}\", uuid::Uuid::new_v4());\n        let imp = importance::compute_importance(fact, &MemoryCategory::Core);\n\n        let candidates = memory.recall(fact, 10, None, None, None).await?;\n        let candidates = dedup::core_candidates(candidates);\n        match dedup::dedup_gate(&candidates, fact, memory_config) {\n            DedupAction::Insert => {}\n            DedupAction::Reject { dup_of } => {\n                ::zeroclaw_log::record!(\n                    DEBUG,\n                    ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Note)\n                        .with_attrs(::serde_json::json!({\"duplicate_of\": dup_of})),\n                    \"memory fact skipped as duplicate\"\n                );\n                continue;\n            }","sourceCodeStart":298,"sourceCodeEnd":334,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-memory/src/consolidation.rs#L298-L334","documentation":"When memory_config.consolidation_extract_facts is enabled, consolidate_turn calls store_typed_facts, which writes each extracted atomic fact as an individual Core memory. Every fact write passes the same fail-closed policy gate as the primary update (policy_gate::validate_store on namespace \"default\", category Core), and any PolicyViolation aborts the whole consolidation with this message. The gate fails on read_only_namespaces containing \"default\", on max_entries_per_namespace, or on max_entries_per_category being reached (and it fails closed to 'quota exceeded' if count_in_scope errors).","triggerScenarios":"Turning on consolidation_extract_facts while policy.read_only_namespaces includes \"default\", or letting namespace/category entry counts reach the configured max_entries_per_namespace / max_entries_per_category before a turn with extracted facts is consolidated. Called from consolidate_turn, so it surfaces through run_consolidation/run just like the primary-write denial.","commonSituations":"Enabling fact extraction experimentally on a config that was hardened read-only for safety; long-lived installs whose Core category fills to its quota so every subsequent turn fails consolidation; treating fact extraction as free and enabling it on agents whose memory namespace is protected.","solutions":["Remove \"default\" from memory.policy.read_only_namespaces if autonomous Core fact writes are acceptable.","Raise max_entries_per_namespace / max_entries_per_category or prune old Core entries so facts fit under quota.","If you do not actually need atomic fact extraction, set consolidation_extract_facts = false — store_typed_facts (and this gate) is then skipped entirely.","If facts are optional for you, catch this error at the consolidation boundary and log-and-continue the turn instead of failing the whole consolidation."],"exampleFix":"# before: facts enabled but namespace protected\nconsolidation_extract_facts = true\n[policy]\nread_only_namespaces = [\"default\"]\n\n# after: pick one coherent stance\nconsolidation_extract_facts = false   # or drop \"default\" from read_only_namespaces","handlingStrategy":"validation","validationCode":"// Only enable fact extraction when the policy actually allows Core writes\nlet policy = PolicyEnforcer::new(&memory_config.policy);\nlet facts_allowed = policy_gate::validate_store(memory, &policy, \"default\", &MemoryCategory::Core).await.is_ok();\nlet mut cfg = memory_config.clone();\nif !facts_allowed {\n    cfg.consolidation_extract_facts = false; // skip store_typed_facts instead of failing the turn\n}","typeGuard":null,"tryCatchPattern":"// Facts are additive: isolate their failure from the primary update\nif let Err(e) = consolidation::consolidate_turn(memory, cfg, &result).await {\n    if e.to_string().starts_with(\"memory fact write denied by policy\") {\n        tracing::warn!(error = %e, \"typed facts skipped: policy denies Core writes\");\n    } else {\n        return Err(e);\n    }\n}","preventionTips":["Add a config-lint rule: consolidation_extract_facts = true requires \"default\" not in read_only_namespaces and quota headroom in the Core category.","Treat fact extraction as optional telemetry — decide up front whether its failure should fail the turn, and encode that in the catch.","Watch Core category counts when facts are on: each turn can add up to MAX_TYPED_FACTS_PER_TURN entries.","Cover this gate in tests by setting read_only_namespaces = [\"default\"] like the repo's own test does."],"tags":["rust","zeroclaw","memory","consolidation","fact-extraction","policy","quota"],"backgroundTag":"policy-write-denied","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}