# Audit Report: Memory-Aware Extraction

**Spec:** `.vibeflow/specs/memory-aware-extraction.md`
**Date:** 2026-03-30
**Verdict: PASS**

## Tests
- ruff check: PASS
- ruff format: PASS
- mypy: PASS (0 errors in 43 source files)
- pytest: PASS (393 passed, 0 failed)

## DoD Checklist

- [x] **1. Alias lookup funciona sem LLM** — `informed_extract.py:113-156` implementa `alias_lookup()` que faz SELECT no `MemoryEntityAlias`, skipa aliases < 3 chars, e faz word boundary matching via regex. Nenhuma chamada LLM. Testado em `test_informed_extract.py::TestAliasLookup` (5 testes).

- [x] **2. Pré-retrieval monta contexto existente** — `informed_extract.py:163-239` implementa `build_extraction_context()` que busca top-K fatos por pgvector cosine distance, filtra por min similarity 0.3, formata como texto, e respeita budget de tokens via `_est_tokens()`. Config params `informed_extraction_topk` e `informed_extraction_context_budget_tokens` em `config.py:19-21`. Testado em `test_informed_extract.py::TestBuildExtractionContext` (4 testes).

- [x] **3. Extração informada produz output completo em 1 chamada** — `informed_extract.py:57-108` define `INFORMED_EXTRACTION_PROMPT` que pede entities + facts (com action + importance_category) + relations num único JSON. `informed_extract_from_message()` em `informed_extract.py:394-473` orquestra alias lookup → context → LLM call → parse. `ExtractedFact` em `constants.py:265-267` tem campos `action`, `importance_category`, `updates`. Testado em `test_informed_extract.py::TestInformedExtraction` (4 testes).

- [x] **4. Pipeline usa extração informada como default** — `pipeline.py:226-245` tenta `informed_extract_from_message()` quando `config.enable_informed_extraction=True` (default). `pipeline.py:364-372` usa `build_informed_decisions()` em vez de `reconcile_facts()` quando `mode == "informed"`. Reconciliação é efetivamente skippada no path informado.

- [x] **5. Fallback pra extração cega funciona** — `pipeline.py:243-245` catch Exception no informed, loga warning, reseta meta. `pipeline.py:248-255` executa blind extraction quando `mode != "informed"`. `config.py:19` permite `enable_informed_extraction=False` pra desabilitar completamente. E2E test atualizado em `test_e2e.py:103` confirma que o path cego continua funcional.

- [x] **6. Cold start equivalente** — `alias_lookup()` retorna dict vazio quando não há aliases no banco → `build_extraction_context()` recebe lista vazia → retorna "" → prompt recebe "EXISTING KNOWLEDGE: None" → extração funciona normalmente. Testado em `test_informed_extract.py::TestInformedExtraction::test_cold_start_works`.

- [x] **7. Segue patterns do projeto** — LLM calls via `_call_llm` reutilizado de `extract.py` (`informed_extract.py:14` import). `temperature=0`, `response_format=json_object` via `_call_llm`. `strip_markdown_fences` + `json.loads` + Pydantic `.model_validate`. Fail-safe: pipeline catch → fallback. Prompt como constante module-level (`INFORMED_EXTRACTION_PROMPT`). Pipeline não faz commit. Savepoints usados em `pipeline.py:312`. Logging via `logger = logging.getLogger(__name__)`.

## Pattern Compliance

- [x] **Pipeline Orchestration** — `run_write_pipeline()` orquestra stages sequencialmente. Timing via `time.perf_counter()`. Trace via `trace.add_step()` com metadata atualizada pra incluir `mode` e `context_entities`. Pipeline não faz commit. Savepoint externo em `session.begin_nested()`. Evidência: `pipeline.py:196,223,257-272,312`.

- [x] **LLM Interaction** — `_call_llm()` helper reutilizado (import de `extract.py`). `temperature=0`, `response_format=json_object`. `strip_markdown_fences()` + `json.loads()` + Pydantic validation. Fail-safe: retorna defaults (Exception propagada ao pipeline que faz fallback). Token timeout via `asyncio.wait_for`. Evidência: `informed_extract.py:14,440-452`.

- [x] **Data Access** — Alias lookup usa `select()` do SQLAlchemy. Pré-retrieval usa raw `text()` SQL com pgvector `<=>` operator. Padrão idêntico ao de `reconcile.py`. `_find_update_target` usa o mesmo padrão. Evidência: `informed_extract.py:125-134,189-204,367-384`.

- [x] **Error Handling** — `informed_extract_from_message` propaga exceções (caller faz fallback). Pipeline faz try/except com `logger.warning`. Fail-safe: blind extraction como fallback completo. Nenhuma exceção propagada ao caller final. Evidência: `pipeline.py:228-245,443-467`.

## Convention Violations
Nenhuma.

## Notes
- `reconcile.py` recebeu 1 linha (campo `importance: float = 0.5` no ReconciliationDecision). Backward compatible — todos os callers existentes usam o default. O spec diz "inalterado como fallback" — o módulo continua funcional como fallback sem qualquer mudança de comportamento.
- `upsert.py` recebeu 2 alterações pontuais (`importance=0.5` → `importance=decision.importance`). Com o default de 0.5 no ReconciliationDecision, o comportamento é idêntico ao anterior para o path de reconciliação.
- `test_e2e.py` recebeu 1 linha (`enable_informed_extraction=False`) pra manter compatibilidade com mocks do fluxo antigo.
