refactor(evidence): remove legacy Python layout (#31)
This commit is contained in:
@@ -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"]
|
||||
@@ -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"]
|
||||
|
||||
@@ -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 +0,0 @@
|
||||
"""Canonical, transport-independent Evidence corpus."""
|
||||
@@ -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
-1
@@ -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
|
||||
@@ -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}$")
|
||||
@@ -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",
|
||||
]
|
||||
@@ -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"]
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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",
|
||||
]
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user