fix(s31): close 1 review BLOCKER — EmbedderConfig registry + --embedder-config, never an import path
This commit is contained in:
parent
cabe05acf3
commit
b9dd91cdbe
4 changed files with 240 additions and 1 deletions
|
|
@ -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 ---
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue