{"record":{"id":"e32055064efe9bc8","repo":"affaan-m/ECC","slug":"context-graph-observation-summary-cannot-be-empty","errorCode":null,"errorMessage":"Context graph observation summary cannot be empty","messagePattern":"Context graph observation summary cannot be empty","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"warning","filePath":"ecc2/src/session/store.rs","lineNumber":3385,"sourceCode":"    }\n\n    pub fn add_context_observation(\n        &self,\n        session_id: Option<&str>,\n        entity_id: i64,\n        observation_type: &str,\n        priority: ContextObservationPriority,\n        pinned: bool,\n        summary: &str,\n        details: &BTreeMap<String, String>,\n    ) -> Result<ContextGraphObservation> {\n        if observation_type.trim().is_empty() {\n            return Err(anyhow::anyhow!(\n                \"Context graph observation type cannot be empty\"\n            ));\n        }\n        if summary.trim().is_empty() {\n            return Err(anyhow::anyhow!(\n                \"Context graph observation summary cannot be empty\"\n            ));\n        }\n\n        let now = chrono::Utc::now().to_rfc3339();\n        let details_json = serde_json::to_string(details)?;\n        self.conn.execute(\n            \"INSERT INTO context_graph_observations (\n                session_id, entity_id, observation_type, priority, pinned, summary, details_json, created_at\n             ) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)\",\n            rusqlite::params![\n                session_id,\n                entity_id,\n                observation_type.trim(),\n                priority.as_db_value(),\n                pinned as i64,\n                summary.trim(),\n                details_json,","sourceCodeStart":3367,"sourceCodeEnd":3403,"githubUrl":"https://github.com/affaan-m/ECC/blob/01e15490f04e29cfefe3896951f43db46994d8ee/ecc2/src/session/store.rs#L3367-L3403","documentation":"Raised by add_context_observation in ecc2/src/session/store.rs:3385 when summary.trim().is_empty(). The summary is the human-readable body of an observation; an empty summary would produce useless entries, so it is rejected after the type check and before the INSERT.","triggerScenarios":"Passing summary = \"\" or whitespace; a caller that builds summary from optional detail fields that are all empty; importing observations with blank summaries.","commonSituations":"Auto-generated observations where the summarizer produced nothing; UI submissions where the summary field is left blank; bulk import with sparse data.","solutions":["Validate summary is non-empty after trim at the call site; require at least one meaningful character.","If auto-generating, fall back to a default like \"(no summary)\" or skip the observation.","During import, quarantine rows with empty summaries."],"exampleFix":"// before\nstore.add_context_observation(Some(sid), entity_id, otype, priority, pinned, \"\", &details)?;\n\n// after: require a meaningful summary\nlet summary = summary.trim();\nif summary.is_empty() {\n    anyhow::bail!(\"observation summary is required for entity {entity_id}\");\n}\nstore.add_context_observation(Some(sid), entity_id, otype, priority, pinned, summary, &details)?;","handlingStrategy":"validation","validationCode":"// Validating newtype for observation summary.\n#[derive(Debug, Clone)]\npub struct ObservationSummary(String);\n\nimpl ObservationSummary {\n    pub fn new(raw: &str) -> anyhow::Result<Self> {\n        let trimmed = raw.trim();\n        if trimmed.is_empty() {\n            anyhow::bail!(\"observation summary cannot be empty\");\n        }\n        Ok(Self(trimmed.to_string()))\n    }\n    pub fn as_str(&self) -> &str { &self.0 }\n}\n\nlet summary = ObservationSummary::new(raw)?;\nstore.add_context_observation(sid, entity_id, otype, priority, pinned, summary.as_str(), &details)?;","typeGuard":"// ObservationSummary (above) is the type guard; it cannot represent an\n// empty string.","tryCatchPattern":"for record in observation_import {\n    let summary = match ObservationSummary::new(&record.summary) {\n        Ok(s) => s,\n        Err(_) => { tracing::warn!(\"skipping observation with empty summary on entity {}\", record.entity_id); continue; }\n    };\n    store.add_context_observation(sid, record.entity_id, otype, record.priority, record.pinned, summary.as_str(), &record.details)?;\n}","preventionTips":["Wrap observation summary in a validating newtype.","For auto-generated summaries, fall back to a default like '(no summary)' or skip the observation.","Skip blank-summary records during import.","Require a meaningful summary at the UI layer."],"tags":["database","context-graph","validation","observation","empty-string"],"backgroundTag":null,"analyzedSha":"01e15490f04e29cfefe3896951f43db46994d8ee","analyzedAt":"2026-08-13T00:31:08.655Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}