import json import uuid from datetime import UTC, datetime from types import SimpleNamespace from typer.testing import CliRunner from tht.cli import app from tht.decisions import DecisionInput from tht.memory import MemoryRecord, recall_memories, save_registry from tht.phase import current_phase from tht.session.filesystem_repository import FilesystemSessionRepository from tht.session.models import PrincipalContext, SessionManifest def _memory(id_: str, type_: str = "concept_clarified") -> MemoryRecord: return MemoryRecord( id=id_, ts=datetime(2026, 8, 24, tzinfo=UTC), session_id="source-session", decision_seq=int(id_.split("-")[1]), type=type_, subject=f"subject {id_}", detail=f"detail {id_}", rationale=f"rationale {id_}", question_context="source question", concepts=[f"concept {id_}"], ) class Embedder: def __init__(self): self.questions = [] def embed_query(self, question): self.questions.append(question) return [0.1, 0.2] class Searcher: def __init__(self, hits): self.hits = hits self.calls = [] def search(self, embedding, *, top_n, kinds): self.calls.append((embedding, top_n, kinds)) return self.hits def test_recall_preserves_rank_and_public_payload_for_reusable_memories(): records = [_memory("mem-0001"), _memory("mem-0002", "table_promoted"), _memory("mem-0003")] searcher = Searcher([ SimpleNamespace(ref="mem-0003", similarity=0.93456), SimpleNamespace(ref="mem-0002", similarity=0.92345), SimpleNamespace(ref="orphan", similarity=0.91234), SimpleNamespace(ref="mem-0001", similarity=0.87654), ]) embedder = Embedder() results = recall_memories( "active patients", records=records, decisions=[], searcher=searcher, embedder=embedder, top=5, ) assert [result["id"] for result in results] == ["mem-0003", "mem-0001"] assert results[0] == { "id": "mem-0003", "type": "concept_clarified", "subject": "subject mem-0003", "detail": "detail mem-0003", "rationale": "rationale mem-0003", "question_context": "source question", "tables": [], "concepts": ["concept mem-0003"], "session_id": "source-session", "score": 0.9346, } assert embedder.questions == ["active patients"] assert searcher.calls == [([0.1, 0.2], 5, ["memory"])] def _workspace_config(tmp_path): config = tmp_path / "workspace.yaml" config.write_text( f""" runtime_identity: workspace_id: psd-clinical workspace_revision: {'a' * 40} dwh: type: postgres_direct connection: {{database: analytics, schema: mart, user: reader, password: secret}} vectors: type: qdrant base_url: http://qdrant:6333 collection: psd-clinical roots: sessions: {tmp_path / 'sessions'} artifacts: {tmp_path / 'artifacts'} indexes: {tmp_path / 'indexes'} embeddings: provider: ollama_internal base_url: http://embedding:11434 model: qwen3-embedding:0.6b dim: 1024 """ ) return config def test_recall_cli_reconstructs_applied_and_rejected_memory_from_persisted_f2_session( tmp_path, monkeypatch ): monkeypatch.setenv("THT_HOME", str(tmp_path / "home")) repository = FilesystemSessionRepository( tmp_path / "home", "psd-clinical", PrincipalContext(issuer="local", subject="reviewer"), root=tmp_path / "sessions", ) session_id = str(uuid.uuid4()) repository.create(SessionManifest( id=session_id, created_at=datetime(2026, 8, 24, tzinfo=UTC), question="active patients", database="analytics", schema="mart", )) repository.append_decisions(session_id, [ DecisionInput(type="phase_approved", subject="phase:1"), DecisionInput( type="concept_clarified", subject="active patient", rationale="Applied from mem-0003", ), DecisionInput( type="memory_rejected", subject="mem-0001", rationale="Not relevant to the resumed question", ), ]) records = [_memory("mem-0001"), _memory("mem-0003")] searcher = Searcher([ SimpleNamespace(ref="mem-0003", similarity=0.9), SimpleNamespace(ref="mem-0001", similarity=0.8), ]) embedder = Embedder() save_registry(records, tmp_path / "artifacts" / "memory" / "registry.jsonl") monkeypatch.setattr("tht.cli.vector_cmd.open_searcher", lambda cfg: searcher) monkeypatch.setattr("tht.cli.vector_cmd.make_embedder", lambda cfg: embedder) response = CliRunner().invoke( app, [ "memory", "search", "active patients", "--session", session_id, "--json", "-c", str(_workspace_config(tmp_path)), ], ) assert response.exit_code == 0, response.output assert json.loads(response.stdout) == [] assert current_phase(repository.get(session_id)) == 2 assert embedder.questions == ["active patients"] assert searcher.calls == [([0.1, 0.2], 5, ["memory"])]