# Audit Report: Relation Lifecycle — Parte 1: Integridade Fato↔Relação

**Verdict: PASS**

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

---

### DoD Checklist

- [x] **DoD 1 — `upsert_relations()` recebe fatos e popula `evidence_fact_id`**
  - `upsert_relations()` aceita `created_facts: dict[str, list[MemoryFact]] | None = None` (upsert.py L483).
  - Chama `_match_relation_to_fact()` para cada relação (L524-529).
  - `evidence_fact_id` incluído no INSERT values (L548-549) e no ON CONFLICT set (L560).
  - `pipeline.py` L233: passa `created_facts=upsert_result.created_facts`.

- [x] **DoD 2 — Match heurístico funciona**
  - `_match_relation_to_fact()` (upsert.py L85-136): busca fatos por `entity_key` de source e target (L107-108), verifica `fact_text.lower()` contém ambos `display_name.lower()` (L124-125), seleciona maior confidence (L134-135).
  - 6 testes unitários: match encontrado, sem match, dict vazio, múltiplos → maior confidence, match por target key, case insensitive.

- [x] **DoD 3 — `_update_fact()` cascateia invalidação**
  - `_update_fact()` (upsert.py L205-213): chama `_cascade_invalidate_relations(session, decision.matched_fact_id, now)` após invalidar o fato antigo. Wrapped em try/except com `logger.warning()`.

- [x] **DoD 4 — `_delete_fact()` cascateia invalidação**
  - `_delete_fact()` (upsert.py L272-280): chama `_cascade_invalidate_relations(session, decision.matched_fact_id, now)` após invalidar o fato. Wrapped em try/except com `logger.warning()`.

- [x] **DoD 5 — Novos testes cobrem evidence linkage e cascata**
  - `tests/test_relation_lifecycle.py`: 11 testes em 3 classes:
    - `TestMatchRelationToFact` (6 testes): match correto, sem match (2 cenários), múltiplos candidatos, match por target key, case insensitive.
    - `TestCascadeInvalidateRelations` (3 testes): cascata invalida relações, zero match retorna 0, não toca já-invalidadas.
    - `TestUpsertResultCreatedFacts` (2 testes): default vazio, acumulação por entity_key.

- [x] **DoD 6 — Documentação atualizada EN + PT-BR**
  - `docs/en/concepts/write-pipeline.md`: Seção "Evidence Linkage" adicionada sob "Relationship Tracking" (L213-223). Descreve match heurístico, cascade, e exemplo concreto.
  - `docs/pt-BR/concepts/write-pipeline.md`: Seção "Vinculação de Evidência" adicionada (L213-223). Mesma estrutura em PT-BR.

---

### Pattern Compliance

- [x] **Pipeline Orchestration** — Seguido corretamente.
  - `pipeline.py` passa `upsert_result.created_facts` como parâmetro explícito (L233). Sem shared state.
  - `upsert_relations()` recebe todos os dados via parâmetros, não faz commit.

- [x] **Data Access** — Seguido corretamente.
  - `_cascade_invalidate_relations()` usa `select() + modify + add` (upsert.py L66-75), consistente com o padrão do projeto.
  - Deduplicação por `fact.id` em `_match_relation_to_fact()` (L111-118).
  - `session.flush()` em `_add_fact()` e `_update_fact()` garante IDs.

- [x] **Error Handling** — Seguido corretamente.
  - Cascata wrapped em try/except com `logger.warning()` em `_update_fact()` (L206-213) e `_delete_fact()` (L273-280). Falha não propaga.
  - Match heurístico wrapped em try/except com `logger.debug()` em `upsert_relations()` (L524-536). Falha → `evidence_fact_id = None`.

- [x] **Testing** — Seguido corretamente.
  - `MagicMock(spec=MemoryFact)` via `make_fact()` de conftest.
  - `AsyncMock()` para session mock.
  - `MagicMock(spec=MemoryEntityRelationship)` para relações mock.
  - Class-based grouping (`TestMatchRelationToFact`, `TestCascadeInvalidateRelations`).
  - File naming: `test_relation_lifecycle.py`.

---

### Convention Violations

Nenhuma violação encontrada. O código segue:
- Naming: `snake_case` funções, `_` prefix privado, `UPPER_SNAKE_CASE` constantes.
- Docstrings: Google-style com `Args:` e `Returns:`.
- Type hints: `str | None` union syntax, `uuid.UUID | None`.
- Logging: `logger.info()` para milestones, `logger.warning()` para erros recuperáveis, `logger.debug()` para diagnósticos.
- Imports: Sorted (ruff-compliant).

---

### Budget

Arquivos alterados: **5 / ≤ 6** ✅
1. `src/arandu/write/upsert.py` (modified)
2. `src/arandu/write/pipeline.py` (modified)
3. `tests/test_relation_lifecycle.py` (created)
4. `docs/en/concepts/write-pipeline.md` (modified)
5. `docs/pt-BR/concepts/write-pipeline.md` (modified)

### Anti-scope

Todos os itens de anti-escopo respeitados: **SIM** ✅
- Sem fatos-espelho, sem alteração em extract.py, sem EXCLUSIVE_REL_TYPES, sem alteração em models.py, sem alteração em graph_retrieval.py, sem alteração em constants.py, sem backfill, sem alteração em `.vibeflow/` internos.
