can1357/oh-my-pi · error · TypeError

compute_fact_id: must be a str, got

Error message

compute_fact_id: ${name} must be a str, got ${typeof value}

What it means

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.

Solutions

  1. Coerce each component with String(value) (or fix the source data) before calling computeFactId
  2. Validate inputs at the boundary and skip/repair triples with non-string components
  3. Check the data source for null/missing columns and normalize on read

Example fix

// before
const id = computeFactId(row.s, row.p, row.o); // row.o may be null
// after
const id = computeFactId(String(row.s), String(row.p), String(row.o ?? "")); // or skip when o == null
Defensive patterns

Strategy: type-guard

Validate before calling

function assertTripleStrings(s, p, o) {
	for (const [name, v] of [["subject", s], ["predicate", p], ["object", o]]) {
		if (typeof v !== "string") throw new TypeError(`compute_fact_id: ${name} must be a string`);
	}
}

Type guard

function isStringTriple(t) {
	return typeof t.subject === "string" && typeof t.predicate === "string" && typeof t.object === "string";
}

Try / catch

try {
	const id = computeFactId(s, p, o);
} catch (err) {
	if (err instanceof TypeError && err.message.startsWith("compute_fact_id:")) {
		logger.warn("skipping triple with non-string component", { s, p, o });
		return null;
	}
	throw err;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/44b0ad41f4ad6bcf. Report an issue: GitHub.

Appendix: source

Thrown at packages/mnemopi/src/core/veracity-consolidation.ts:121

		}
		return out;
	} catch {
		return [];
	}
}

function nowIso(): string {
	return new Date().toISOString();
}

export function computeFactId(subject: string, predicate: string, object: string): string {
	for (const [name, value] of [
		["subject", subject],
		["predicate", predicate],
		["object", object],
	] as const) {
		if (typeof value !== "string") {
			throw new TypeError(`compute_fact_id: ${name} must be a str, got ${typeof value}`);
		}
		if (value === "") throw new RangeError(`compute_fact_id: ${name} must be non-empty`);
	}

	const chunks: Buffer[] = [];
	for (const value of [subject, predicate, object]) {
		const bytes = Buffer.from(value.normalize("NFC"), "utf8");
		chunks.push(Buffer.from(`${bytes.length}:`, "ascii"), bytes);
	}
	return `cf_${createHash("sha256").update(Buffer.concat(chunks)).digest("hex").slice(0, 24)}`;
}
export function clampVeracity(raw: unknown, context = "veracity"): Veracity {
	if (raw === null || raw === undefined) return "unknown";
	const norm = String(raw).trim().toLowerCase();
	if (norm === "") return "unknown";
	if (isVeracity(norm)) return norm;
	const rawString = String(raw);
	const rawForLog =

View on GitHub (pinned to 9690622007)