Coverage for node / src / stigmem_node / recall / fuzzy_resolver.py: 100%
19 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-06-18 05:34 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-06-18 05:34 +0000
1"""Fuzzy entity resolver — spec §2.6 Phase 6.
33-layer resolver:
4 Layer 1: strict normalizer (entity_normalizer.py) — deterministic case + whitespace.
5 Layer 2: explicit alias table lookup — user-defined semantic equivalences stored in
6 entity_aliases (e.g. user:alice ≡ user:a.smith).
7 Layer 3: passthrough — returns the Layer 1 result when no alias is registered.
9The strict normalizer is stateless and import-time only; this module adds the
10DB-backed Layer 2 on top and exposes helpers for alias registration / lookup.
12Ingest contract: callers MUST apply normalize_entity_uri first, then call
13resolve_entity with the already-normalized URI and an open connection. This
14keeps the two concerns separable and avoids a second DB round-trip on the hot
15normalization path when no alias table exists.
16"""
18from __future__ import annotations
20import sqlite3
21from datetime import UTC, datetime
22from typing import Any
24from ..utility.entity_normalizer import NormalizationError, normalize_entity_uri
27def resolve_entity(
28 conn: sqlite3.Connection, normalized_uri: str, tenant_id: str = "default"
29) -> str:
30 """Layer 2 alias lookup. Input MUST already be Layer 1–normalized.
32 Returns canonical_uri from the caller's tenant aliases if a registered alias
33 exists, otherwise returns normalized_uri unchanged (Layer 3 passthrough).
34 """
35 row = conn.execute(
36 "SELECT canonical_uri FROM entity_aliases WHERE raw_uri = ? AND tenant_id = ?",
37 (normalized_uri, tenant_id),
38 ).fetchone()
39 return str(row["canonical_uri"]) if row else normalized_uri
42def register_alias(
43 conn: sqlite3.Connection,
44 raw_uri: str,
45 canonical_uri: str,
46 *,
47 kind: str = "user",
48 tenant_id: str = "default",
49) -> dict[str, Any]:
50 """Register or replace a semantic alias (raw_uri resolves to canonical_uri).
52 Both URIs are Layer 1–normalized before storage so the caller need not
53 pre-normalize them. The alias is scoped to ``tenant_id``. Raises ValueError
54 on empty input or identical endpoints.
56 Returns the stored alias record as a plain dict.
57 """
58 try:
59 norm_raw = normalize_entity_uri(raw_uri)
60 norm_canonical = normalize_entity_uri(canonical_uri)
61 except NormalizationError as exc:
62 raise ValueError(str(exc)) from exc
64 if norm_raw == norm_canonical:
65 raise ValueError(f"raw_uri and canonical_uri must differ after normalization: {norm_raw!r}")
67 now = datetime.now(UTC).isoformat()
68 conn.execute(
69 "INSERT OR REPLACE INTO entity_aliases"
70 " (raw_uri, canonical_uri, kind, created_at, tenant_id) VALUES (?, ?, ?, ?, ?)",
71 (norm_raw, norm_canonical, kind, now, tenant_id),
72 )
73 return {
74 "raw_uri": norm_raw,
75 "canonical_uri": norm_canonical,
76 "kind": kind,
77 "created_at": now,
78 }