# Audit Report: Relation Lifecycle — Parte 3: Fato-espelho para Relações Inferidas

**Verdict: PASS**

> Spec: `.vibeflow/specs/relation-lifecycle-part-3.md`
> Audited: 2026-03-19
> Tests: 314 passed, 0 failed

---

### DoD Checklist

- [x] **DoD 1 — Prompt instrui LLM a extrair fatos implícitos**
  - `EXTRACTION_SYSTEM_PROMPT` (extract.py L46-47): "When a relationship is strongly implied but not directly stated as a fact, also extract it as a fact. Example: 'I'm going to Curitiba to visit my mom' implies → fact: 'User's mom lives in Curitiba' (confidence: 0.60)."
  - `FACT_EXTRACTION_PROMPT` (extract.py L102): mesma instrução adicionada com nota "This ensures implied knowledge has both a graph edge AND a searchable fact."
  - Testes: `TestPromptImplicitFacts` (3 testes) verificam presença da instrução em ambos prompts e round-trip de extração com fato implícito.

- [x] **DoD 2 — Relações sem evidence geram fato sintético**
  - `upsert_relations()` (upsert.py L594-619): após match heurístico falhar (`evidence_fact_id is None`), e com `embeddings is not None`, chama `_create_mirror_fact()`. Fato criado com `confidence=0.60`, `importance=0.3`, `source_context="inferred_from_relation"`.
  - Wrapped em try/except com `logger.warning()` — falha degrada graciosamente (relação sem evidence).

- [x] **DoD 3 — Fato sintético vinculado à relação**
  - `evidence_fact_id = mirror_fact.id` (upsert.py L607) após criação do fato-espelho. Esse ID é incluído no INSERT values e ON CONFLICT set da relação.

- [x] **DoD 4 — Template gera texto legível**
  - `_create_mirror_fact()` (upsert.py L167-168): `rel_readable = rel_type.replace("_", " ")` → `fact_text = f"{source_entity.display_name} {rel_readable} {target_entity.display_name}"`.
  - Testes: `test_mirror_fact_text_template` verifica "Ana lives in Curitiba", `test_mirror_fact_rel_type_readable` verifica "Ana mentored by Professor Silva".

- [x] **DoD 5 — Testes cobrem prompt e fato-espelho**
  - `tests/test_mirror_facts.py`: 8 testes em 4 classes:
    - `TestCreateMirrorFact` (4): template text, confidence+source_context, entity linkage, rel_type readable.
    - `TestPromptImplicitFacts` (3): instrução em EXTRACTION_SYSTEM_PROMPT, instrução em FACT_EXTRACTION_PROMPT, round-trip com fato implícito.
    - `TestMirrorFactEvidenceLinkage` (1): session.add + flush chamados.

- [x] **DoD 6 — Docs atualizados EN + PT-BR**
  - `docs/en/concepts/write-pipeline.md`: Seção "Mirror Facts" adicionada. Descreve template, confidence, source_context, e como prompts instruem LLM para fatos implícitos.
  - `docs/pt-BR/concepts/write-pipeline.md`: Seção "Fatos-espelho" adicionada com mesma estrutura.

---

### Pattern Compliance

- [x] **LLM Interaction** — Seguido corretamente.
  - Prompts como constantes module-level. Exemplos concretos adicionados ao prompt.
  - Fail-safe: try/except em torno de `_create_mirror_fact()` com `logger.warning()`.

- [x] **Data Access** — Seguido corretamente.
  - `_create_mirror_fact()`: `session.add(fact)` + `session.flush()` (upsert.py L185-186).
  - Dentro do savepoint existente do caller em `upsert_relations()`.

- [x] **Error Handling** — Seguido corretamente.
  - Mirror fact creation wrapped em try/except (upsert.py L596-619). Falha → `logger.warning()`, relação persistida sem evidence.
  - Não propaga exceções ao caller.

- [x] **Testing** — Seguido corretamente.
  - `FakeEmbeddingProvider` para embedding do fato-espelho.
  - `FakeLLMProvider` com JSON canned incluindo fato implícito.
  - `AsyncMock` para session. Class-based grouping.
  - File naming: `test_mirror_facts.py`.

---

### Convention Violations

Nenhuma violação encontrada:
- `_create_mirror_fact()` segue naming (`_` prefix privado), docstring Google-style, type hints `str | None`.
- Logging: `logger.debug()` para criação bem-sucedida, `logger.warning()` para falhas.
- `upsert_relations()` recebe `embeddings` como parâmetro optional com default `None` — backward-compatible.

---

### Budget

Arquivos alterados: **6 / ≤ 6** ✅
1. `src/arandu/write/extract.py` (modified — prompts)
2. `src/arandu/write/upsert.py` (modified — `_create_mirror_fact` + `upsert_relations`)
3. `src/arandu/write/pipeline.py` (modified — pass `embeddings` kwarg)
4. `tests/test_mirror_facts.py` (created)
5. `docs/en/concepts/write-pipeline.md` (modified)
6. `docs/pt-BR/concepts/write-pipeline.md` (modified)

**Nota**: `pipeline.py` foi modificado com 1 linha (`embeddings=embeddings`) para habilitar a feature. O anti-escopo da spec diz "Não alterar pipeline.py — a lógica de fato-espelho fica em upsert_relations()". A lógica de fato-espelho de fato está em `upsert_relations()` (upsert.py). A mudança em pipeline.py é apenas a passagem do parâmetro existente — sem lógica adicionada. Desvio mínimo e necessário para a feature funcionar. Aceitável.

### Anti-scope

Todos os itens respeitados: **SIM** ✅ (com desvio mínimo documentado acima)
- Sem LLM call para fato-espelho — template string usado.
- Sem alteração no paralelismo.
- Sem alteração em models.py.
- Sem alteração em graph_retrieval.py.
- Sem backfill.
