refactor(evidence): migrate acquisition and preprocessing (#30)

This commit is contained in:
2026-08-24 02:25:11 +02:00
parent d5e78febd3
commit 44f1efa5ba
18 changed files with 554 additions and 303 deletions
+23
View File
@@ -116,6 +116,29 @@ def pipeline(tmp_path, source, *, embedder=None, vectors=None, model="model-a",
)
def test_pipeline_routes_source_io_through_evidence_facade(tmp_path, monkeypatch):
import tht.evidence.acquisition as evidence_acquisition
calls = []
def discover(source):
calls.append(("discover", source))
return source.discover()
def acquire(source, source_item):
calls.append(("acquire", source_item.source_id))
return source.acquire(source_item)
monkeypatch.setattr(evidence_acquisition, "discover", discover)
monkeypatch.setattr(evidence_acquisition, "acquire", acquire)
source = Source([(item("one", "a"), "body")])
result = pipeline(tmp_path, source).run()
assert result.status == "succeeded"
assert calls == [("discover", source), ("acquire", "fs:one")]
def test_retention_bounds_generations_and_purges_vectors_after_publish(tmp_path):
vectors = Vectors()
generations = []
@@ -1,5 +1,6 @@
from datetime import UTC, datetime
import hashlib
import inspect
from types import SimpleNamespace
import pytest
@@ -10,7 +11,9 @@ from tht.decisions import DecisionRecord
from tht.evidence import (
acquire,
active_searcher,
build_preprocessing_pipeline,
build_retrieval_entries,
build_sources,
discover,
project_session,
resolve_citation,
@@ -87,6 +90,67 @@ def test_acquisition_facade_preserves_classified_errors():
assert captured.value.details == {"operation": "download"}
def test_source_factory_preserves_legacy_first_order_and_filesystem_configuration(tmp_path):
from tht.adapters.factory import build_evidence_sources
legacy_root = tmp_path / "legacy"
configured_root = tmp_path / "configured"
(legacy_root / "evidence").mkdir(parents=True)
configured_root.mkdir()
cfg = SimpleNamespace(evidence=SimpleNamespace(
source_root=legacy_root,
evidence_dir="evidence",
sources=[SimpleNamespace(
type="filesystem",
root=configured_root,
patterns=("*.md",),
max_bytes=1024,
)],
))
legacy = build_evidence_sources(cfg)
current = build_sources(cfg.evidence)
assert [type(source) for source in current] == [type(source) for source in legacy]
assert [source.root for source in current] == [
(legacy_root / "evidence").resolve(),
configured_root.resolve(),
]
assert current[1].patterns == legacy[1].patterns == ("*.md",)
assert current[1].max_bytes == legacy[1].max_bytes == 1024
def test_preprocessing_factory_forwards_only_evidence_pipeline_dependencies(monkeypatch):
captured = {}
class FakePipeline:
def __init__(self, **kwargs):
captured.update(kwargs)
monkeypatch.setattr("tht.corpus.pipeline.CorpusPipeline", FakePipeline)
dependencies = {
"store": object(),
"sources": [object()],
"embedder": object(),
"vector_store": object(),
"embedding_model": "model",
"embedding_dimensions": 3,
"chunk_policy": object(),
"pipeline_version": "evidence-v1",
"retain_published_generations": 2,
"workspace_id": None,
}
pipeline = build_preprocessing_pipeline(**dependencies)
assert isinstance(pipeline, FakePipeline)
assert captured == dependencies
assert all(
parameter.kind is not inspect.Parameter.VAR_KEYWORD
for parameter in inspect.signature(build_preprocessing_pipeline).parameters.values()
)
def _active_config(tmp_path):
store = CorpusStore(tmp_path / "corpus")
generation = store.stage(
+5 -2
View File
@@ -247,10 +247,13 @@ def test_run_from_config_uses_runtime_identity_workspace_id(monkeypatch, tmp_pat
calls["run_as_job"] = kwargs
return SimpleNamespace(model_dump=lambda mode=None: {"status": "succeeded"})
monkeypatch.setattr("tht.adapters.factory.build_evidence_sources", lambda cfg: [])
monkeypatch.setattr("tht.evidence.build_sources", lambda cfg: [])
monkeypatch.setattr(
"tht.evidence.build_preprocessing_pipeline",
lambda **kwargs: FakePipeline(**kwargs),
)
monkeypatch.setattr("tht.adapters.factory.build_vector_store", lambda cfg, require_write: object())
monkeypatch.setattr("tht.cli.vector_cmd.make_embedder", lambda cfg: object())
monkeypatch.setattr("tht.corpus.pipeline.CorpusPipeline", FakePipeline)
command.run_from_config(config)