{"record":{"id":"d5aa3732ae04e544","repo":"zeroclaw-labs/zeroclaw","slug":"proposal-quarantined-reason","errorCode":null,"errorMessage":"proposal {} quarantined: {reason}","messagePattern":"proposal (.+?) quarantined: (.+?)","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-runtime/src/sop/procedural_memory.rs","lineNumber":182,"sourceCode":"            engine.save_proposal(&proposal)?;\n            bail!(\"proposal {} is stale; target SOP now exists\", proposal.id);\n        }\n        (_, Some(expected)) if current_target_hash.as_ref() != Some(expected) => {\n            proposal.status = ProposalStatus::Stale;\n            proposal.updated_at = now_iso8601();\n            proposal.status_reason = Some(\"target SOP changed since proposal capture\".into());\n            engine.save_proposal(&proposal)?;\n            bail!(\"proposal {} is stale; inspect and re-propose\", proposal.id);\n        }\n        _ => {}\n    }\n\n    if let Some(reason) = scan_candidate(&proposal.manifest_toml, &proposal.procedure_markdown) {\n        proposal.status = ProposalStatus::Quarantined;\n        proposal.updated_at = now_iso8601();\n        proposal.status_reason = Some(reason.clone());\n        engine.save_proposal(&proposal)?;\n        bail!(\"proposal {} quarantined: {reason}\", proposal.id);\n    }\n\n    validate_candidate(\n        &proposal.sop_name,\n        &proposal.manifest_toml,\n        &proposal.procedure_markdown,\n    )?;\n    let rollback = write_rollback(&sops_root, &target_dir, &proposal.id)?;\n    atomic_write_sop(\n        &target_dir,\n        &proposal.manifest_toml,\n        &proposal.procedure_markdown,\n    )?;\n\n    proposal.status = ProposalStatus::Applied;\n    proposal.updated_at = now_iso8601();\n    proposal.applied_at = Some(proposal.updated_at.clone());\n    proposal.applied_by = applied_by;","sourceCodeStart":164,"sourceCodeEnd":200,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-runtime/src/sop/procedural_memory.rs#L164-L200","documentation":"apply_proposal re-runs scan_candidate over the stored proposal content at apply time (defense in depth beyond the capture-time scan). If the LeakDetector finds credential-like content, the proposal is persisted as Quarantined with the detection reason in status_reason, and the apply bails. The proposal is preserved for inspection but is no longer appliable.","triggerScenarios":"Applying a proposal whose manifest/procedure content contains a credential-like pattern that was introduced after capture or that the capture-time scan missed (e.g. detector updates, or content edited into the record).","commonSituations":"LeakDetector rules tightened between capture and apply; step outputs whose secrets survived scrubbing (long token= values); proposals held in review while their content aged against newer detection rules.","solutions":["Load the quarantined proposal and read status_reason to see which pattern matched.","If it is a true positive, rotate the credential and create a new proposal with cleaned content.","If it is a false positive, reword the flagged text and re-propose; never hand-edit the record back to Pending.","Re-apply promptly after capture to shrink the window for rule-skew mismatches."],"exampleFix":"// before: apply assumes pending proposals are always appliable\napply_proposal(&engine, install_root, id, None).await?;\n\n// after: handle quarantine explicitly\nmatch apply_proposal(&engine, install_root, id, None).await {\n    Ok(out) => out,\n    Err(e) if e.to_string().contains(\"quarantined\") => {\n        let p = engine.load_proposal(id)?.context(\"proposal missing\")?;\n        tracing::warn!(reason = ?p.status_reason, \"proposal quarantined; re-propose cleaned content\");\n        return Err(e);\n    }\n    Err(e) => return Err(e),\n}","handlingStrategy":"validation","validationCode":"// Pre-scan the stored proposal content with the same heuristic before applying.\nfn looks_like_credential(text: &str) -> bool {\n    for line in text.lines() {\n        let l = line.to_ascii_lowercase();\n        for key in [\"token=\", \"key=\", \"secret=\", \"password=\"] {\n            if let Some(pos) = l.find(key) {\n                let value = &line[pos + key.len()..];\n                if value.chars().take_while(|c| c.is_ascii_graphic()).count() >= 20 {\n                    return true;\n                }\n            }\n        }\n    }\n    false\n}\n\nlet p = engine.load_proposal(id)?.context(\"proposal missing\")?;\nif looks_like_credential(&format!(\"{}\\n{}\", p.manifest_toml, p.procedure_markdown)) {\n    anyhow::bail!(\"clean the proposal content and re-propose\");\n}\napply_proposal(&engine, install_root, id, None).await?;","typeGuard":null,"tryCatchPattern":"match apply_proposal(&engine, install_root, id, None).await {\n    Err(e) if e.to_string().contains(\"quarantined\") => {\n        // reload the record: status_reason names the matched pattern; re-propose cleaned content\n        let p = engine.load_proposal(id)?.context(\"proposal missing\")?;\n        tracing::warn!(reason = ?p.status_reason, \"quarantined at apply time\");\n        return Err(e);\n    }\n    other => other?,\n}","preventionTips":["Keep the propose-to-apply window short so detector rule changes cannot strand proposals.","Never hand-edit a quarantined record back to Pending; re-propose cleaned content.","Rotate any real credential that matched before handling the proposal further."],"tags":["procedural-memory","proposal","quarantine","secret-detection"],"backgroundTag":"secret-leak-detected","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}