{"record":{"id":"409139200b98165d","repo":"can1357/oh-my-pi","slug":"compute-fact-id-name-must-be-non-empty","errorCode":null,"errorMessage":"compute_fact_id: ${name} must be non-empty","messagePattern":"compute_fact_id: (.+?) must be non-empty","errorType":"validation","errorClass":"RangeError","httpStatus":null,"severity":"error","filePath":"packages/mnemopi/src/core/veracity-consolidation.ts","lineNumber":123,"sourceCode":"\t} catch {\n\t\treturn [];\n\t}\n}\n\nfunction nowIso(): string {\n\treturn new Date().toISOString();\n}\n\nexport function computeFactId(subject: string, predicate: string, object: string): string {\n\tfor (const [name, value] of [\n\t\t[\"subject\", subject],\n\t\t[\"predicate\", predicate],\n\t\t[\"object\", object],\n\t] as const) {\n\t\tif (typeof value !== \"string\") {\n\t\t\tthrow new TypeError(`compute_fact_id: ${name} must be a str, got ${typeof value}`);\n\t\t}\n\t\tif (value === \"\") throw new RangeError(`compute_fact_id: ${name} must be non-empty`);\n\t}\n\n\tconst chunks: Buffer[] = [];\n\tfor (const value of [subject, predicate, object]) {\n\t\tconst bytes = Buffer.from(value.normalize(\"NFC\"), \"utf8\");\n\t\tchunks.push(Buffer.from(`${bytes.length}:`, \"ascii\"), bytes);\n\t}\n\treturn `cf_${createHash(\"sha256\").update(Buffer.concat(chunks)).digest(\"hex\").slice(0, 24)}`;\n}\nexport function clampVeracity(raw: unknown, context = \"veracity\"): Veracity {\n\tif (raw === null || raw === undefined) return \"unknown\";\n\tconst norm = String(raw).trim().toLowerCase();\n\tif (norm === \"\") return \"unknown\";\n\tif (isVeracity(norm)) return norm;\n\tconst rawString = String(raw);\n\tconst rawForLog =\n\t\trawString.length > VERACITY_WARN_VALUE_CAP\n\t\t\t? `${rawString.slice(0, VERACITY_WARN_VALUE_CAP)}...[truncated]`","sourceCodeStart":105,"sourceCodeEnd":141,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/mnemopi/src/core/veracity-consolidation.ts#L105-L141","documentation":"computeFactId also rejects empty-string components for subject, predicate, or object with a RangeError, because an empty component would collide different facts into the same hash. The check runs after the type check, so blank strings from trimmed/missing data reach this error.","triggerScenarios":"computeFactId(\"\", \"knows\", \"bob\") or any component set to \"\"; data rows where a field was empty in the source; .split() or regex extraction producing empty captures; over-eager trimming reducing a value to \"\".","commonSituations":"Importing sparse spreadsheet/CSV data with blank cells; extraction pipelines emitting empty matches; forms submitted with required fields blank; normalizing whitespace-only strings to \"\".","solutions":["Skip or repair triples with empty components before hashing (validate each field is non-empty)","Fix the extraction/import step so empty values don't become triples","Provide a real sentinel value if \"unknown\" semantics are intended, rather than \"\""],"exampleFix":"// before\nconst id = computeFactId(s.trim(), p, o); // s may be \"\"\n// after\nif (!s.trim() || !p || !o) continue; // skip invalid triple\nconst id = computeFactId(s.trim(), p, o);","handlingStrategy":"validation","validationCode":"function isValidTriple(s, p, o) {\n\treturn [s, p, o].every(v => typeof v === \"string\" && v.length > 0);\n}\nif (!isValidTriple(s, p, o)) skipTriple();\nconst id = computeFactId(s, p, o);","typeGuard":null,"tryCatchPattern":"try {\n\tconst id = computeFactId(s, p, o);\n} catch (err) {\n\tif (err instanceof RangeError && err.message.includes(\"non-empty\")) {\n\t\tlogger.warn(\"skipping triple with empty component\", { s, p, o });\n\t\treturn null;\n\t}\n\tthrow err;\n}","preventionTips":["Filter out empty strings during extraction/import","Don't trim values to empty and still use them — validate after trimming","Use an explicit sentinel like \"(unknown)\" instead of \"\" for missing values","Validate triples at ingestion, not at hashing time"],"tags":["validation","empty-value","hashing"],"backgroundTag":"empty-required-field","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}