{"record":{"id":"95a4c32a62fba1ed","repo":"affaan-m/ECC","slug":"context-graph-entity-name-cannot-be-empty","errorCode":null,"errorMessage":"Context graph entity name cannot be empty","messagePattern":"Context graph entity name cannot be empty","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"warning","filePath":"ecc2/src/session/store.rs","lineNumber":3103,"sourceCode":"        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)\n             ON CONFLICT(entity_key) DO UPDATE SET\n                session_id = COALESCE(excluded.session_id, context_graph_entities.session_id),\n                summary = CASE\n                    WHEN excluded.summary <> '' THEN excluded.summary","sourceCodeStart":3085,"sourceCodeEnd":3121,"githubUrl":"https://github.com/affaan-m/ECC/blob/01e15490f04e29cfefe3896951f43db46994d8ee/ecc2/src/session/store.rs#L3085-L3121","documentation":"Raised by upsert_context_entity in ecc2/src/session/store.rs:3103 when name.trim().is_empty(). The name, together with entity_type and optional path, forms the entity_key via context_graph_entity_key; an empty name would produce ambiguous keys and break lookups, so it is rejected before insert.","triggerScenarios":"Passing an empty or whitespace-only name; a caller that builds name from a filename whose basename is empty; importing entities where the name column is blank.","commonSituations":"Bulk import with sparse data; deriving name from Path::file_name() on a path ending in '/' (returns None -> \"\"); UI input not validated.","solutions":["Validate name at the call site: require non-empty after trim; if name is derived from a path, fall back to the path string or skip the record.","During import, drop or quarantine rows with empty names.","Co-locate validation so upsert_context_entity never receives empty names."],"exampleFix":"// before\nlet name = path.file_name().map(|s| s.to_string_lossy().to_string()).unwrap_or_default();\nstore.upsert_context_entity(None, etype, &name, Some(&path), summary, &meta)?;\n\n// after: guard the derived name\nlet name = path.file_name()\n    .map(|s| s.to_string_lossy().to_string())\n    .unwrap_or_else(|| path.to_string_lossy().to_string());\nif name.trim().is_empty() {\n    anyhow::bail!(\"cannot derive a non-empty entity name from {path}\");\n}\nstore.upsert_context_entity(None, etype, &name, Some(&path), summary, &meta)?;","handlingStrategy":"validation","validationCode":"// Same newtype pattern for the name.\n#[derive(Debug, Clone)]\npub struct ContextEntityName(String);\n\nimpl ContextEntityName {\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 name cannot be empty\");\n        }\n        Ok(Self(trimmed.to_string()))\n    }\n    pub fn as_str(&self) -> &str { &self.0 }\n}\n\n// When deriving name from a path, fall back to the path string itself.\nfn name_from_path(path: &Path) -> anyhow::Result<ContextEntityName> {\n    let raw = path.file_name()\n        .map(|s| s.to_string_lossy().to_string())\n        .unwrap_or_else(|| path.to_string_lossy().to_string());\n    ContextEntityName::new(&raw)\n}","typeGuard":"// ContextEntityName (above) is the type guard: it cannot represent an\n// empty string.","tryCatchPattern":"for record in import_stream {\n    let name = match ContextEntityName::new(&record.name) {\n        Ok(n) => n,\n        Err(_) => { tracing::warn!(\"skipping entity with empty name\"); continue; }\n    };\n    store.upsert_context_entity(sid, etype, name.as_str(), path, summary, &meta)?;\n}","preventionTips":["Wrap entity name in a validating newtype.","When deriving from a path, fall back to the full path string if the basename is empty.","Skip blank-name records during import.","Require a non-empty name at the UI layer."],"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"}