{"record":{"id":"1f6506b6e1ce217c","repo":"affaan-m/ECC","slug":"context-graph-entity-type-cannot-be-empty","errorCode":null,"errorMessage":"Context graph entity type cannot be empty","messagePattern":"Context graph entity type cannot be empty","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"warning","filePath":"ecc2/src/session/store.rs","lineNumber":3099,"sourceCode":"                stats.messages_processed = stats.messages_processed.saturating_add(1);\n            }\n        }\n\n        Ok(stats)\n    }\n\n    pub fn upsert_context_entity(\n        &self,\n        session_id: Option<&str>,\n        entity_type: &str,\n        name: &str,\n        path: Option<&str>,\n        summary: &str,\n        metadata: &BTreeMap<String, String>,\n    ) -> Result<ContextGraphEntity> {\n        let entity_type = entity_type.trim();\n        if entity_type.is_empty() {\n            return Err(anyhow::anyhow!(\"Context graph entity type cannot be empty\"));\n        }\n        let name = name.trim();\n        if name.is_empty() {\n            return Err(anyhow::anyhow!(\"Context graph entity name cannot be empty\"));\n        }\n\n        let normalized_path = path.map(str::trim).filter(|value| !value.is_empty());\n        let summary = summary.trim();\n        let entity_key = context_graph_entity_key(entity_type, name, normalized_path);\n        let metadata_json = serde_json::to_string(metadata)\n            .context(\"Failed to serialize context graph metadata\")?;\n        let timestamp = chrono::Utc::now().to_rfc3339();\n\n        self.conn.execute(\n            \"INSERT INTO context_graph_entities (\n                session_id, entity_key, entity_type, name, path, summary, metadata_json, created_at, updated_at\n             )\n             VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?8)","sourceCodeStart":3081,"sourceCodeEnd":3117,"githubUrl":"https://github.com/affaan-m/ECC/blob/01e15490f04e29cfefe3896951f43db46994d8ee/ecc2/src/session/store.rs#L3081-L3117","documentation":"Raised by upsert_context_entity in ecc2/src/session/store.rs:3099 when entity_type.trim().is_empty(). upsert_context_entity inserts/updates a row in context_graph_entities keyed by (entity_type, name, path); an empty type would corrupt the key and the semantic meaning of the entity, so it is rejected before any SQL runs.","triggerScenarios":"Passing entity_type = \"\" or \"   \" (whitespace only); passing a type derived from user input without trimming/validating; a default value leaking through when a caller forgot to populate the field.","commonSituations":"Bulk-importing context entities from a source where some rows have blank type columns; programmatic callers that build entity_type from an optional field that may be None->\"\".","solutions":["Validate and normalize entity_type at the call site: trim and require a non-empty value, mapping empty to a sensible default like \"note\" or rejecting the record.","If importing, filter out rows with empty types before calling upsert_context_entity.","Add a debug_assert or a UI-level required-field check for entity type."],"exampleFix":"// before\nstore.upsert_context_entity(Some(sid), \"\", name, path, summary, &meta)?;\n\n// after: derive a default and guard\nlet entity_type = entity_type.trim();\nif entity_type.is_empty() {\n    anyhow::bail!(\"entity_type is required for {name}\");\n}\nstore.upsert_context_entity(Some(sid), entity_type, name, path, summary, &meta)?;","handlingStrategy":"validation","validationCode":"// Centralize entity validation so the store never sees an empty type.\n#[derive(Debug, Clone)]\npub struct ContextEntityType(String);\n\nimpl ContextEntityType {\n    pub fn new(raw: &str) -> anyhow::Result<Self> {\n        let trimmed = raw.trim();\n        if trimmed.is_empty() {\n            anyhow::bail!(\"context graph entity type cannot be empty\");\n        }\n        Ok(Self(trimmed.to_string()))\n    }\n    pub fn as_str(&self) -> &str { &self.0 }\n}\n\n// upsert_context_entity takes &ContextEntityType, so callers must construct\n// it through the validating constructor.\nlet etype = ContextEntityType::new(raw_type)?;\nstore.upsert_context_entity(sid, etype.as_str(), name, path, summary, &meta)?;","typeGuard":"// ContextEntityType (above) is the type guard: it cannot represent an\n// empty string, so any value of this type satisfies the store's requirement.","tryCatchPattern":"// Validation happens before the call; the catch is for import pipelines.\nfor record in import_stream {\n    let etype = match ContextEntityType::new(&record.entity_type) {\n        Ok(t) => t,\n        Err(_) => { tracing::warn!(\"skipping entity with empty type: {:?}\", record); continue; }\n    };\n    store.upsert_context_entity(sid, etype.as_str(), &record.name, record.path.as_deref(), &record.summary, &record.meta)?;\n}","preventionTips":["Wrap entity_type in a newtype whose constructor rejects empty/whitespace values.","During import, skip records with empty types rather than forwarding them.","Add a UI-level required field for entity type.","Co-locate validation so the store never receives empty strings."],"tags":["database","context-graph","validation","entity","empty-string"],"backgroundTag":null,"analyzedSha":"01e15490f04e29cfefe3896951f43db46994d8ee","analyzedAt":"2026-08-13T00:31:08.655Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}