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

@ -10,6 +10,7 @@ content so the dry-run reaches its offline return.
from __future__ import annotations
import json
import sys
from pathlib import Path
import pytest
@ -604,6 +605,92 @@ def test_semantic_retrieval_with_both_dirs_is_not_refused(tmp_path, capsys) -> N
assert "--semantic-retrieval" not in out.err
def test_embedder_config_valid_file_parses_offline(tmp_path, capsys) -> None:
"""``--embedder-config <valid>`` is accepted and the run still stops offline — the registry is
reachable from the CLI, not merely importable."""
cfg = tmp_path / "embedder.json"
cfg.write_text('{"type": "fake"}', encoding="utf-8")
rc = run.main(
[
_PID,
"--docs-dir",
str(BUNDLE_DIR),
"--bundle-dir",
str(BUNDLE_DIR),
"--verdict-dir",
str(tmp_path / "inbox"),
"--semantic-retrieval",
"--embedder-config",
str(cfg),
"--live-dry-run",
]
)
assert rc == 0
assert "LIVE-DRY-RUN OK" in capsys.readouterr().out
def test_embedder_config_missing_file_refuses(tmp_path, capsys) -> None:
"""Fail-fast startup config: a missing file refuses the run (rc 1, no traceback), surfacing
through the existing structured-refusal handler."""
rc = run.main(
[
_PID,
"--docs-dir",
str(BUNDLE_DIR),
"--bundle-dir",
str(BUNDLE_DIR),
"--verdict-dir",
str(tmp_path / "inbox"),
"--semantic-retrieval",
"--embedder-config",
"/nonexistent-embedder-config.json",
"--live-dry-run",
]
)
err = capsys.readouterr().err
assert rc == 1
assert "refused" in err.lower()
assert "Traceback" not in err
def test_embedder_config_unknown_type_refuses(tmp_path, capsys) -> None:
"""A config naming an embedder outside the closed registry is REFUSED, never resolved — this
is the CLI-level face of the no-import-path rule."""
cfg = tmp_path / "embedder.json"
cfg.write_text('{"type": "my_pkg.mod:NetworkEmbedder"}', encoding="utf-8")
rc = run.main(
[
_PID,
"--docs-dir",
str(BUNDLE_DIR),
"--bundle-dir",
str(BUNDLE_DIR),
"--verdict-dir",
str(tmp_path / "inbox"),
"--semantic-retrieval",
"--embedder-config",
str(cfg),
"--live-dry-run",
]
)
err = capsys.readouterr().err
assert rc == 1
assert "refused" in err.lower()
assert "my_pkg" not in sys.modules
def test_report_with_embedder_config_is_refused(tmp_path, capsys) -> None:
"""--report stays an ALLOWLIST: a new config flag must be refused there like every other one,
else it would be silently dropped."""
cfg = tmp_path / "embedder.json"
cfg.write_text('{"type": "fake"}', encoding="utf-8")
ledger_file = tmp_path / "ledger.json"
SavingsLedger(entries=[]).save(str(ledger_file))
rc = run.main(["--report", "--ledger", str(ledger_file), "--embedder-config", str(cfg)])
assert rc == 1
assert "refused" in capsys.readouterr().err.lower()
def test_semantic_retrieval_is_not_refused_in_portfolio_mode(capsys) -> None:
"""The flag is valid in BOTH modes (like --dimension-config), so the portfolio partition must
not name it. Probed via a run that IS refused for a different flag: the refusal lists

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 ---