# Spec: Fact-Entity Links — Part 2: Read Pipeline (Retrieval via Links)

> Generated via /vibeflow:gen-spec on 2026-03-23
> PRD: `.vibeflow/prds/fact-entity-links.md`
> Budget: ≤ 5 files

## Objective

O retrieval passa a encontrar facts por qualquer entidade mencionada (via `MemoryFactEntityLink`), não só pelo subject primário — eliminando a dependência de duplicação cross-entity.

## Context

Part 1 criou o modelo `MemoryFactEntityLink` e popula links no write. Mas o read pipeline ainda busca por `MemoryFact.entity_key`. Um fact "Clara saiu da Vertix" com `entity_key=person:clara_rezende` e link pra `organization:vertix` não é encontrado quando se pergunta sobre Vertix — o retrieval ainda usa o campo singular.

## Definition of Done

1. **[ ] Graph retrieval usa links** — `retrieve_graph_facts()` busca facts via `MemoryFactEntityLink.entity_key` em vez de `MemoryFact.entity_key`. BFS 2-hop continua funcionando.
2. **[ ] Retrieval por entidade secundária funciona** — Query "O que aconteceu com a Vertix?" retorna fact "Clara saiu da Vertix" (que tem `entity_key=person:clara_rezende` mas link pra `organization:vertix`).
3. **[ ] Sem duplicatas no resultado** — Se um fact é linkado a 2 entidades que aparecem na query, o fact aparece UMA vez no resultado (dedup por `fact.id`).
4. **[ ] Backward compat** — Se `MemoryFactEntityLink` está vazia (banco sem migration), retrieval funciona via `MemoryFact.entity_key` (fallback).
5. **[ ] Testes passam** — Existentes + novos cobrindo: retrieval via link, dedup, fallback.

## Scope

### 1. `src/arandu/read/graph_retrieval.py` — Query via links

Mudar a query de facts por entity de:
```sql
SELECT ... FROM memory_facts WHERE entity_key = :ek
```
Para:
```sql
SELECT ... FROM memory_facts f
WHERE f.id IN (
    SELECT fact_id FROM memory_fact_entity_links
    WHERE user_id = :uid AND entity_key = :ek
)
```

Fallback: se a subquery retorna 0 resultados, tentar o query antigo (`WHERE entity_key = :ek`).

### 2. `src/arandu/read/retrieval.py` — Semantic + keyword retrieval

O `retrieve_candidates()` hoje não filtra por entity — faz cosine similarity global. Não precisa mudar. Mas o `_compute_entity_signal()` (se existir) precisa usar links.

### 3. `src/arandu/read/spreading_activation.py` — `_fetch_neighbor_facts` via links

Mudar `_fetch_neighbor_facts()` de:
```sql
WHERE entity_key = :ek
```
Para query via links (mesma pattern do graph_retrieval).

### 4. Testes

- `test_graph_retrieval_via_link` — fact com link secundário é encontrado
- `test_graph_retrieval_dedup` — fact com 2 links aparece 1x
- `test_graph_retrieval_fallback` — sem links, usa entity_key

### 5. Docs EN + PT-BR

Atualizar read-pipeline.md com explicação de como links melhoram retrieval cross-entity.

## Anti-scope

- **Modelo `MemoryFactEntityLink`** — já criado em Part 1
- **Write pipeline** — já cria links em Part 1
- **Reconciliation** — não mudar
- **Reranker** — não mudar (recebe candidates, não sabe de entities)
- **Query expansion** — não mudar
- **Retrieval agent (planner)** — não mudar

## Technical Decisions

### TD-1: Subquery EXISTS vs JOIN
**Trade-off:** JOIN pode duplicar rows se fact tem N links. EXISTS não duplica.
**Decisão:** Usar `EXISTS (SELECT 1 FROM links WHERE links.fact_id = f.id AND links.entity_key = :ek)` pra evitar row duplication sem GROUP BY.

### TD-2: Fallback pra entity_key direto
**Trade-off:** Manter fallback adiciona complexidade. Mas garante que bancos sem migration continuam funcionando.
**Decisão:** Fallback ativo. Se links query retorna 0 e entity_key query retorna >0, usar entity_key. Log warning sugerindo migration.

## Applicable Patterns

- **Data Access** — async SQLAlchemy, `select().where(exists(...))`
- **Error Handling** — fallback graceful pra backward compat
- **Testing** — mock-based

## Risks

1. **Performance: subquery EXISTS** — Mitigação: índice em `(user_id, entity_key)` no link table. EXISTS com índice é O(log N).
2. **Fallback mascarando bug** — Se links não são criados corretamente, fallback esconde o problema. Mitigação: log warning no fallback.

## Dependencies

- `.vibeflow/specs/fact-entity-links-part-1.md`
