fix(s31): close 1 review BLOCKER — EmbedderConfig registry + --embedder-config, never an import path

This commit is contained in:
Kjell Tore Guttormsen 2026-07-25 12:50:19 +02:00
commit b9dd91cdbe
4 changed files with 240 additions and 1 deletions

View file

@ -21,13 +21,18 @@ from pathlib import Path
import numpy as np
import pytest
from pydantic import ValidationError
from portfolio_optimiser.semretrieval import (
EMBED_DIM,
Embedder,
EmbedderConfig,
FakeEmbedder,
HybridRanker,
StructuralRetriever,
build_embedder,
cosine,
load_embedder_config,
load_vector_store,
save_vector_store,
)
@ -291,6 +296,61 @@ def test_hybrid_ranker_at_weight_zero_equals_the_structural_ranking() -> None:
]
# --- The embedder config seam: a CLOSED registry, never an import path -------------------------
def test_build_embedder_returns_the_shipped_fake_for_the_known_type() -> None:
"""The registry's one shipped entry. Mirrors ``tests/test_notify.py``'s build_notifier tests."""
built = build_embedder(EmbedderConfig(type="fake"))
assert isinstance(built, FakeEmbedder)
assert isinstance(built, Embedder) # satisfies the runtime-checkable protocol
def test_build_embedder_rejects_an_unknown_type() -> None:
"""Closed vocabulary: an unknown type RAISES rather than falling back to a default, and the
message names the seam so the constraint is discoverable at the failure."""
with pytest.raises(ValueError, match="unknown embedder type"):
build_embedder(EmbedderConfig(type="nope"))
def test_build_embedder_refuses_an_import_path_instead_of_importing_it() -> None:
"""SECURITY — the registry must never resolve a dotted ``module:Class`` string from config.
Doing so would be arbitrary code execution at config-load time and would hand a config file the
ability to import a module that opens a socket routing around this module's no-network guard,
which is scoped to ``semretrieval.py`` and blind to a third module by construction. The config
is accepted as DATA and then refused; nothing is imported."""
hostile = EmbedderConfig(type="evil_pkg.embedders:NetworkEmbedder")
with pytest.raises(ValueError, match="unknown embedder type"):
build_embedder(hostile)
assert "evil_pkg" not in sys.modules
def test_embedder_config_rejects_an_empty_type() -> None:
with pytest.raises(ValidationError):
EmbedderConfig(type="")
def test_load_embedder_config_is_fail_fast_on_a_missing_file(tmp_path: Path) -> None:
"""Authoritative startup config — absence RAISES, in deliberate contrast to the tolerant
verdict-inbox RAW layer which skips bad files."""
with pytest.raises(FileNotFoundError):
load_embedder_config(tmp_path / "nope.json")
def test_load_embedder_config_is_fail_fast_on_a_malformed_file(tmp_path: Path) -> None:
bad = tmp_path / "embedder.json"
bad.write_text('{"type": 17}', encoding="utf-8")
with pytest.raises(ValidationError):
load_embedder_config(bad)
def test_load_embedder_config_round_trips_a_valid_file(tmp_path: Path) -> None:
good = tmp_path / "embedder.json"
good.write_text('{"type": "fake"}', encoding="utf-8")
assert isinstance(build_embedder(load_embedder_config(good)), FakeEmbedder)
# --- SC3 / SC7: the vector store is byte-deterministic and fails fast on a row/line mismatch ---