refactor(evidence): remove legacy Python layout (#31)

This commit is contained in:
2026-08-24 02:36:30 +02:00
parent 44f1efa5ba
commit 840848df94
36 changed files with 246 additions and 321 deletions
@@ -1,7 +0,0 @@
"""Evidence source adapter implementations."""
from tht.adapters.evidence.filesystem import FilesystemEvidenceSource
from tht.adapters.evidence.http import HttpManifestEvidenceSource
from tht.adapters.evidence.s3 import S3EvidenceSource
__all__ = ["FilesystemEvidenceSource", "HttpManifestEvidenceSource", "S3EvidenceSource"]
+1 -8
View File
@@ -42,11 +42,4 @@ def build_vector_store(cfg: Config, *, require_write: bool = False) -> VectorSto
raise ConfigError(f"Adapter vector non supportato: {other}")
def build_evidence_sources(cfg: Config):
"""Compatibility shim for callers not yet migrated to ``tht.evidence``."""
from tht.evidence import build_sources
return build_sources(cfg.evidence)
__all__ = ["build_dwh", "build_evidence_sources", "build_vector_store"]
__all__ = ["build_dwh", "build_vector_store"]
+4 -4
View File
@@ -96,8 +96,8 @@ def run_from_config(config: Path, *, dry_run: bool = False, resume: str | None =
from tht.adapters.factory import build_vector_store
from tht.cli.schema_cmd import _load_config_or_exit
from tht.cli.vector_cmd import make_embedder
from tht.corpus.chunk import ChunkPolicy
from tht.corpus.store import CorpusStore
from tht.evidence.corpus.chunk import ChunkPolicy
from tht.evidence.corpus.store import CorpusStore
from tht.evidence import build_preprocessing_pipeline, build_sources
cfg = _load_config_or_exit(config)
@@ -130,8 +130,8 @@ def gc_from_config(config: Path, *, dry_run: bool = False):
from tht.adapters.factory import build_vector_store
from tht.cli.schema_cmd import _load_config_or_exit
from tht.cli.vector_cmd import make_embedder
from tht.corpus.chunk import ChunkPolicy
from tht.corpus.store import CorpusStore
from tht.evidence.corpus.chunk import ChunkPolicy
from tht.evidence.corpus.store import CorpusStore
from tht.evidence import build_preprocessing_pipeline, build_sources
cfg = _load_config_or_exit(config)
-1
View File
@@ -1 +0,0 @@
"""Canonical, transport-independent Evidence corpus."""
+15 -150
View File
@@ -1,14 +1,4 @@
"""Cohesive public entrypoint for Evidence domain capabilities.
The implementation is introduced beside the legacy module layout so production callers can
migrate one path at a time. These functions deliberately preserve the existing objects, ordering,
and exceptions; they do not define a cross-domain service protocol.
"""
from __future__ import annotations
from pathlib import Path
from typing import TYPE_CHECKING
"""Cohesive public entrypoint for Evidence domain capabilities."""
from tht.evidence.acquisition import acquire, discover
from tht.evidence.contracts import (
@@ -22,145 +12,17 @@ from tht.evidence.contracts import (
validate_namespaced_value,
validate_safe_metadata,
)
if TYPE_CHECKING:
from tht.config import EvidenceSourcesConfig
from tht.corpus.chunk import ChunkPolicy
from tht.corpus.pipeline import CorpusPipeline
from tht.corpus.store import CorpusStore
from tht.decisions import DecisionRecord
from tht.evidence.preprocessing import EvidenceEmbedder
from tht.ports.vector import VectorStore
from tht.search.evidence import ActiveEvidenceSearcher
from tht.session.models import SchemaLinking
def build_sources(evidence: "EvidenceSourcesConfig | None") -> list[EvidenceSource]:
"""Build configured Evidence source adapters in the existing deterministic order."""
from tht.evidence.sources import build_sources as build_configured_sources
return build_configured_sources(evidence)
def build_preprocessing_pipeline(
*,
store: "CorpusStore",
sources: list[EvidenceSource],
embedder: "EvidenceEmbedder",
vector_store: "VectorStore",
embedding_model: str,
embedding_dimensions: int,
chunk_policy: "ChunkPolicy",
pipeline_version: str,
retain_published_generations: int = 3,
workspace_id: str | None = None,
) -> "CorpusPipeline":
"""Construct the Evidence preprocessing use case from core-owned infrastructure."""
from tht.evidence.preprocessing import build_preprocessing_pipeline as build_pipeline
return build_pipeline(
store=store,
sources=sources,
embedder=embedder,
vector_store=vector_store,
embedding_model=embedding_model,
embedding_dimensions=embedding_dimensions,
chunk_policy=chunk_policy,
pipeline_version=pipeline_version,
retain_published_generations=retain_published_generations,
workspace_id=workspace_id,
)
def active_searcher(
cfg,
delegate,
*,
workspace_id: str | None = None,
) -> ActiveEvidenceSearcher:
"""Bind vector search to the atomically ACTIVE Evidence corpus generation."""
from tht.search.evidence import active_searcher as legacy_active_searcher
return legacy_active_searcher(cfg, delegate, workspace_id=workspace_id)
def validate_corpus_workspace(cfg, workspace_id: str) -> None:
"""Validate persisted Evidence corpus ownership before runtime retrieval setup."""
from tht.search.evidence import validate_corpus_workspace as legacy_validate
legacy_validate(cfg, workspace_id)
def build_retrieval_entries(results, *, excerpt_chars: int) -> list[dict]:
"""Project ordered Evidence search hits into the existing retrieval-pack shape."""
return [
{
"title": result.label,
"status": result.status,
"excerpt": result.content[:excerpt_chars],
}
for result in results
]
def resolve_citation(
store: "CorpusStore",
evidence_id: str,
*,
materialized_root: Path | None = None,
) -> str:
"""Resolve an Evidence identifier to its immutable ACTIVE materialization."""
from tht.search.evidence import resolve_evidence_file
return resolve_evidence_file(
store,
evidence_id,
materialized_root=materialized_root,
)
def _resolve_session_citation(evidence_root: Path, evidence_id: str) -> str:
# New deployments resolve only immutable materialized files from ACTIVE. Keep the
# curated-tree fallback for sessions created before a canonical corpus exists.
corpus_root = evidence_root.parent.parent / "corpus"
if corpus_root.exists():
from tht.corpus.store import CorpusStore
return resolve_citation(
CorpusStore(corpus_root),
evidence_id,
materialized_root=evidence_root.parent / ".materialized-evidence",
)
for match in evidence_root.rglob(f"{evidence_id}.md"):
return str(match)
return ""
def project_session(
decisions: list["DecisionRecord"],
linking: "SchemaLinking",
evidence_root: Path,
) -> list[dict]:
"""Project cited and reviewed Evidence into the existing session artifact shape."""
entries: dict[str, dict] = {}
for candidate in linking.candidates:
for evidence_id in candidate.evidence:
entries.setdefault(evidence_id, {
"id": evidence_id,
"file": _resolve_session_citation(evidence_root, evidence_id),
"esito": "usata",
"decision_seq": candidate.decision_seq,
})
for decision in decisions:
if decision.type not in ("evidence_accepted", "evidence_rejected"):
continue
entries[decision.subject] = {
"id": decision.subject,
"file": _resolve_session_citation(evidence_root, decision.subject),
"esito": "accettata" if decision.type == "evidence_accepted" else "scartata",
"decision_seq": decision.seq,
}
return list(entries.values())
from tht.evidence.preprocessing import EvidenceEmbedder, build_preprocessing_pipeline
from tht.evidence.search import (
ActiveEvidenceSearcher,
CorpusWorkspaceMismatchError,
active_searcher,
build_retrieval_entries,
resolve_citation,
validate_corpus_workspace,
)
from tht.evidence.session import project_session
from tht.evidence.sources import build_sources
__all__ = [
@@ -168,7 +30,10 @@ __all__ = [
"EvidenceSource",
"EvidenceSourceError",
"EvidenceSourceErrorCategory",
"EvidenceEmbedder",
"SourceObject",
"ActiveEvidenceSearcher",
"CorpusWorkspaceMismatchError",
"acquire",
"active_searcher",
"build_preprocessing_pipeline",
@@ -0,0 +1,7 @@
"""Evidence-owned source adapter implementations."""
from tht.evidence.adapters.filesystem import FilesystemEvidenceSource
from tht.evidence.adapters.http import HttpManifestEvidenceSource
from tht.evidence.adapters.s3 import S3EvidenceSource
__all__ = ["FilesystemEvidenceSource", "HttpManifestEvidenceSource", "S3EvidenceSource"]
@@ -1,4 +1,4 @@
"""Contained, race-safe filesystem Evidence source."""
"""Evidence-owned, race-safe filesystem source."""
import hashlib
import os
@@ -1,4 +1,4 @@
"""Explicit-manifest HTTP Evidence source with SSRF-safe bounded acquisition."""
"""Evidence-owned explicit-manifest HTTP source with SSRF-safe bounded acquisition."""
import hashlib
import ipaddress
@@ -1,4 +1,4 @@
"""Bounded S3-compatible Evidence source using the supported boto3 client."""
"""Evidence-owned bounded S3-compatible source using the supported boto3 client."""
import hashlib
import ipaddress
+1
View File
@@ -0,0 +1 @@
"""Evidence-owned canonical, transport-independent corpus."""
@@ -1,11 +1,11 @@
"""Versioned deterministic chunking for canonical corpus documents."""
"""Evidence-owned deterministic chunking for canonical corpus documents."""
import hashlib
import json
import re
from dataclasses import asdict, dataclass
from tht.corpus.models import CanonicalChunk, CanonicalDocument
from tht.evidence.corpus.models import CanonicalChunk, CanonicalDocument
@dataclass(frozen=True, slots=True)
@@ -1,4 +1,4 @@
"""Immutable records emitted by the Evidence preprocessing pipeline."""
"""Evidence-owned immutable records emitted by preprocessing."""
import hashlib
import re
@@ -1,4 +1,4 @@
"""Pure, deterministic conversion of acquired bytes into canonical text."""
"""Evidence-owned deterministic conversion of acquired bytes into canonical text."""
import hashlib
import re
@@ -10,7 +10,7 @@ from pydantic import JsonValue, TypeAdapter, ValidationError
from yaml.events import AliasEvent
from yaml.nodes import MappingNode
from tht.corpus.models import CanonicalDocument
from tht.evidence.corpus.models import CanonicalDocument
from tht.evidence.contracts import AcquiredDocument, canonical_provenance_uri
@@ -1,4 +1,4 @@
"""Incremental Evidence preprocessing with generation-isolated vector writes."""
"""Evidence-owned incremental preprocessing with generation-isolated vector writes."""
from __future__ import annotations
@@ -11,10 +11,10 @@ from dataclasses import asdict, dataclass, field
from datetime import UTC
from pathlib import Path
from tht.corpus.chunk import ChunkPolicy, chunk
from tht.corpus.models import CanonicalChunk, CanonicalDocument, CorpusManifest
from tht.corpus.normalize import normalize
from tht.corpus.store import CorpusStore
from tht.evidence.corpus.chunk import ChunkPolicy, chunk
from tht.evidence.corpus.models import CanonicalChunk, CanonicalDocument, CorpusManifest
from tht.evidence.corpus.normalize import normalize
from tht.evidence.corpus.store import CorpusStore
import tht.evidence.acquisition as evidence_acquisition
from tht.evidence.contracts import EvidenceSource, SourceObject, canonical_provenance_uri
from tht.ports.vector import VectorStore, VectorWriteRecord
@@ -1,4 +1,4 @@
"""Durable immutable corpus generations and an atomic ACTIVE pointer."""
"""Evidence-owned immutable corpus generations and an atomic ACTIVE pointer."""
from __future__ import annotations
@@ -15,7 +15,7 @@ from datetime import UTC, datetime
from pathlib import Path
from contextlib import contextmanager
from tht.corpus.models import CorpusManifest
from tht.evidence.corpus.models import CorpusManifest
_GENERATION = re.compile(r"^gen:[0-9a-f]{32}$")
+3 -3
View File
@@ -2,9 +2,9 @@
from typing import Protocol
from tht.corpus.chunk import ChunkPolicy
from tht.corpus.pipeline import CorpusPipeline
from tht.corpus.store import CorpusStore
from tht.evidence.corpus.chunk import ChunkPolicy
from tht.evidence.corpus.pipeline import CorpusPipeline
from tht.evidence.corpus.store import CorpusStore
from tht.evidence.contracts import EvidenceSource
from tht.ports.vector import VectorStore
@@ -1,8 +1,8 @@
"""Runtime Evidence lookup bound to the atomically active corpus generation."""
"""Evidence-owned runtime lookup bound to the atomically active corpus generation."""
import re
from tht.corpus.store import CorpusStore
from tht.evidence.corpus.store import CorpusStore
class CorpusWorkspaceMismatchError(RuntimeError):
@@ -96,7 +96,7 @@ def validate_corpus_workspace(cfg, workspace_id: str) -> None:
)
def resolve_evidence_file(
def resolve_citation(
store: CorpusStore, evidence_id: str, *, materialized_root=None,
) -> str:
with store.writer_lock():
@@ -114,3 +114,25 @@ def resolve_evidence_file(
)
return str(path) if path else ""
return ""
def build_retrieval_entries(results, *, excerpt_chars: int) -> list[dict]:
"""Project ordered Evidence search hits into the retrieval-pack shape."""
return [
{
"title": result.label,
"status": result.status,
"excerpt": result.content[:excerpt_chars],
}
for result in results
]
__all__ = [
"ActiveEvidenceSearcher",
"CorpusWorkspaceMismatchError",
"active_searcher",
"build_retrieval_entries",
"resolve_citation",
"validate_corpus_workspace",
]
+59
View File
@@ -0,0 +1,59 @@
"""Evidence-specific projection into persisted session artifacts."""
from pathlib import Path
from typing import TYPE_CHECKING
from tht.evidence.corpus.store import CorpusStore
from tht.evidence.search import resolve_citation
if TYPE_CHECKING:
from tht.decisions import DecisionRecord
from tht.session.models import SchemaLinking
def _resolve_session_citation(evidence_root: Path, evidence_id: str) -> str:
# New deployments resolve only immutable materialized files from ACTIVE. Keep the
# curated-tree fallback for sessions created before a canonical corpus exists.
corpus_root = evidence_root.parent.parent / "corpus"
if corpus_root.exists():
return resolve_citation(
CorpusStore(corpus_root),
evidence_id,
materialized_root=evidence_root.parent / ".materialized-evidence",
)
for match in evidence_root.rglob(f"{evidence_id}.md"):
return str(match)
return ""
def project_session(
decisions: list["DecisionRecord"],
linking: "SchemaLinking",
evidence_root: Path,
) -> list[dict]:
"""Project cited and reviewed Evidence into the session artifact shape."""
entries: dict[str, dict] = {}
for candidate in linking.candidates:
for evidence_id in candidate.evidence:
entries.setdefault(
evidence_id,
{
"id": evidence_id,
"file": _resolve_session_citation(evidence_root, evidence_id),
"esito": "usata",
"decision_seq": candidate.decision_seq,
},
)
for decision in decisions:
if decision.type not in ("evidence_accepted", "evidence_rejected"):
continue
entries[decision.subject] = {
"id": decision.subject,
"file": _resolve_session_citation(evidence_root, decision.subject),
"esito": "accettata" if decision.type == "evidence_accepted" else "scartata",
"decision_seq": decision.seq,
}
return list(entries.values())
__all__ = ["project_session"]
+11 -3
View File
@@ -1,10 +1,16 @@
"""Construction of configured Evidence source adapters."""
from tht.adapters.evidence import FilesystemEvidenceSource, HttpManifestEvidenceSource
from tht.adapters.evidence.s3 import S3EvidenceSource
from tht.config import ConfigError, EvidenceSourcesConfig
from __future__ import annotations
from typing import TYPE_CHECKING
from tht.evidence.adapters import FilesystemEvidenceSource, HttpManifestEvidenceSource
from tht.evidence.adapters.s3 import S3EvidenceSource
from tht.evidence.contracts import EvidenceSource
if TYPE_CHECKING:
from tht.config import EvidenceSourcesConfig
def build_sources(evidence: EvidenceSourcesConfig | None) -> list[EvidenceSource]:
"""Build configured Evidence adapters in the existing deterministic order."""
@@ -59,6 +65,8 @@ def build_sources(evidence: EvidenceSourcesConfig | None) -> list[EvidenceSource
)
)
case other: # pragma: no cover - Pydantic rejects unsupported discriminators.
from tht.config import ConfigError
raise ConfigError(f"Adapter evidence non supportato: {other}")
return sources
-31
View File
@@ -1,31 +0,0 @@
"""Legacy import path for Evidence contracts.
New production code imports the leaf contracts from ``tht.evidence.contracts``. This one-way
compatibility shim remains only until the old Evidence layout is removed.
"""
from tht.evidence.contracts import (
AcquiredDocument,
EvidenceSource,
EvidenceSourceError,
EvidenceSourceErrorCategory,
SourceObject,
canonical_provenance_uri,
normalize_aware_datetime,
validate_canonical_uri,
validate_namespaced_value,
validate_safe_metadata,
)
__all__ = [
"AcquiredDocument",
"EvidenceSource",
"EvidenceSourceError",
"EvidenceSourceErrorCategory",
"SourceObject",
"canonical_provenance_uri",
"normalize_aware_datetime",
"validate_canonical_uri",
"validate_namespaced_value",
"validate_safe_metadata",
]
-17
View File
@@ -1,17 +0,0 @@
"""Legacy session-artifact entrypoints retained during the Evidence migration."""
from pathlib import Path
from tht.decisions import DecisionRecord
from tht.session.models import SchemaLinking
def build_evidence_entries(
decisions: list[DecisionRecord],
linking: SchemaLinking,
evidence_root: Path,
) -> list[dict]:
"""Compatibility shim for callers not yet migrated to ``tht.evidence``."""
from tht.evidence import project_session
return project_session(decisions, linking, evidence_root)