{"record":{"id":"85dbca38b02397fc","repo":"affaan-m/ECC","slug":"context-graph-observation-type-cannot-be-empty","errorCode":null,"errorMessage":"Context graph observation type cannot be empty","messagePattern":"Context graph observation type cannot be empty","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"warning","filePath":"ecc2/src/session/store.rs","lineNumber":3380,"sourceCode":"        Ok(Some(ContextGraphEntityDetail {\n            entity,\n            outgoing,\n            incoming,\n        }))\n    }\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,","sourceCodeStart":3362,"sourceCodeEnd":3398,"githubUrl":"https://github.com/affaan-m/ECC/blob/01e15490f04e29cfefe3896951f43db46994d8ee/ecc2/src/session/store.rs#L3362-L3398","documentation":"Raised by add_context_observation in ecc2/src/session/store.rs:3380 when observation_type.trim().is_empty(). Observations are typed notes attached to a context-graph entity; the type drives filtering and display, so an empty type is rejected before the INSERT INTO context_graph_observations runs.","triggerScenarios":"Passing observation_type = \"\"; a caller that passes a raw enum variant name that was never set; importing observations from a source with blank type fields.","commonSituations":"Programmatic observation creation where the type is optional and defaulted to empty; bulk import pipelines with sparse columns.","solutions":["Validate observation_type at the call site and require a known, non-empty value.","If importing, skip rows with empty types or map them to a default like \"note\".","Surface a UI-level required field for observation type."],"exampleFix":"// before\nstore.add_context_observation(Some(sid), entity_id, \"\", priority, pinned, summary, &details)?;\n\n// after: enforce a non-empty type\nlet observation_type = observation_type.trim();\nif observation_type.is_empty() {\n    anyhow::bail!(\"observation_type is required for entity {entity_id}\");\n}\nstore.add_context_observation(Some(sid), entity_id, observation_type, priority, pinned, summary, &details)?;","handlingStrategy":"validation","validationCode":"// Validating newtype for observation_type.\n#[derive(Debug, Clone)]\npub struct ObservationType(String);\n\nimpl ObservationType {\n    pub fn new(raw: &str) -> anyhow::Result<Self> {\n        let trimmed = raw.trim();\n        if trimmed.is_empty() {\n            anyhow::bail!(\"observation type cannot be empty\");\n        }\n        Ok(Self(trimmed.to_string()))\n    }\n    pub fn as_str(&self) -> &str { &self.0 }\n}\n\nlet otype = ObservationType::new(raw)?;\nstore.add_context_observation(sid, entity_id, otype.as_str(), priority, pinned, summary, &details)?;","typeGuard":"// ObservationType (above) is the type guard; it cannot represent an empty\n// string, so any value satisfies the store's non-empty requirement.","tryCatchPattern":"for record in observation_import {\n    let otype = match ObservationType::new(&record.observation_type) {\n        Ok(t) => t,\n        Err(_) => { tracing::warn!(\"skipping observation with empty type on entity {}\", record.entity_id); continue; }\n    };\n    store.add_context_observation(sid, record.entity_id, otype.as_str(), record.priority, record.pinned, &record.summary, &record.details)?;\n}","preventionTips":["Wrap observation_type in a validating newtype.","Maintain a controlled vocabulary of observation types and validate against it.","Skip blank-type records during import.","Require the type 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"}