{"record":{"id":"44b0ad41f4ad6bcf","repo":"can1357/oh-my-pi","slug":"compute-fact-id-name-must-be-a-str-got-type","errorCode":null,"errorMessage":"compute_fact_id: ${name} must be a str, got ${typeof value}","messagePattern":"compute_fact_id: (.+?) must be a str, got (.+?)","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"packages/mnemopi/src/core/veracity-consolidation.ts","lineNumber":121,"sourceCode":"\t\t}\n\t\treturn out;\n\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 =","sourceCodeStart":103,"sourceCodeEnd":139,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/mnemopi/src/core/veracity-consolidation.ts#L103-L139","documentation":"computeFactId hashes (subject, predicate, object) into a deterministic fact id and requires each component to be a string. If any argument is not a string (number, null, undefined, object), it throws a TypeError naming the offending field. This guards hash determinism — a non-string would silently produce different or broken ids.","triggerScenarios":"computeFactId(subject, predicate, object) called with a numeric or null component; passing database rows where a column is NULL; passing optional values without defaulting; JS callers bypassing TypeScript types.","commonSituations":"Loading triples from CSV/JSON where subjects were parsed as numbers; a NULL object column in SQLite mapped to null; API responses with missing fields (undefined); template-built predicates that ended up as symbols/objects.","solutions":["Coerce each component with String(value) (or fix the source data) before calling computeFactId","Validate inputs at the boundary and skip/repair triples with non-string components","Check the data source for null/missing columns and normalize on read"],"exampleFix":"// before\nconst id = computeFactId(row.s, row.p, row.o); // row.o may be null\n// after\nconst id = computeFactId(String(row.s), String(row.p), String(row.o ?? \"\")); // or skip when o == null","handlingStrategy":"type-guard","validationCode":"function assertTripleStrings(s, p, o) {\n\tfor (const [name, v] of [[\"subject\", s], [\"predicate\", p], [\"object\", o]]) {\n\t\tif (typeof v !== \"string\") throw new TypeError(`compute_fact_id: ${name} must be a string`);\n\t}\n}","typeGuard":"function isStringTriple(t) {\n\treturn typeof t.subject === \"string\" && typeof t.predicate === \"string\" && typeof t.object === \"string\";\n}","tryCatchPattern":"try {\n\tconst id = computeFactId(s, p, o);\n} catch (err) {\n\tif (err instanceof TypeError && err.message.startsWith(\"compute_fact_id:\")) {\n\t\tlogger.warn(\"skipping triple with non-string component\", { s, p, o });\n\t\treturn null;\n\t}\n\tthrow err;\n}","preventionTips":["Normalize data on read: coerce or reject non-string columns","Type triples as {subject: string; predicate: string; object: string} end-to-end","Handle NULL db columns explicitly instead of passing null through","Avoid `as any` casts that hide non-string values"],"tags":["typescript","validation","type-error"],"backgroundTag":"argument-type-mismatch","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}