# PRD: Write Pipeline DX Fixes

> Generated via /vibeflow:discover on 2026-03-19

## Problem

Dois problemas reportados pela equipe de integração que afetam a confiabilidade e a experiência de quem consome o SDK:

**1. Tabela `memory_entities` esparsa.** O entity resolution só insere rows na `memory_entities` quando cria entities novas (`_create_entity()`). Entities resolvidas via exact match ou fuzzy match não geram upsert na tabela. Resultado: facts e relations referenciam entity keys que não existem em `memory_entities`. Background jobs que dependem dessa tabela (importance scoring, summary refresh, spreading activation) ficam cegos — não conseguem operar sobre entities que não têm row. O tester reportou que após uma mensagem com ~15 pessoas, `memory_entities` tinha apenas 1 row (`user:self`), enquanto 27 entity keys distintas apareciam nos facts.

**2. Inconsistência de tipos no `WriteResult`.** `facts_added`, `facts_updated` e `entities_resolved` retornam `list` com detalhes, mas `facts_deleted` e `facts_unchanged` retornam `int`. Quem consome o SDK não consegue saber quais facts ficaram iguais ou foram deletados — só a contagem. Quebra expectativa e dificulta debugging/observabilidade.

## Target Audience

Desenvolvedores integrando o SDK arandu — equipe de teste atual e futuros consumidores da API pública.

## Proposed Solution

1. **Garantir que toda entity resolvida tenha um row em `memory_entities`.** Após o entity resolution, fazer upsert via `create_or_update_entity()` para cada entity no entity_map — não apenas as novas. Isso garante que exact match e fuzzy match também populem a tabela.

2. **Mudar `facts_deleted` e `facts_unchanged` de `int` para `list`** em `WriteResult` e `WritePipelineResult`. Popular com os detalhes dos facts (mesma estrutura que `facts_added`/`facts_updated`).

## Success Criteria

1. Após um write com N entities distintas, `memory_entities` tem pelo menos N rows para aquele user.
2. `WriteResult.facts_deleted` e `WriteResult.facts_unchanged` retornam listas com detalhes dos facts, não contagens.
3. Todos os testes existentes continuam passando (backward compatibility nos testes que checam contagem precisam ser ajustados).

## Scope v0

- Upsert de entities resolvidas em `memory_entities` dentro de `resolve_entities()` ou no pipeline após o resolve
- Mudar tipo de `facts_deleted` e `facts_unchanged` em `WritePipelineResult` e `WriteResult`
- Popular as listas no upsert stage (facts NOOP e DELETE)
- Ajustar testes existentes que comparam com `int`

## Anti-scope

- Migração retroativa de entities existentes no banco (popular entities antigas que já estão faltando)
- Mudança em background jobs (eles já leem de `memory_entities`, só precisam que ela esteja populada)
- Mudança no read pipeline
- FK constraint entre `MemoryFact.entity_key` e `MemoryEntity.canonical_key` (mudança de schema pesada, não necessária agora)
- Incremento de `fact_count` na `MemoryEntity` durante upsert (melhoria separada)

## Technical Context

- `create_or_update_entity()` em `write/entity_helpers.py` já faz upsert via `ON CONFLICT DO UPDATE` — safe pra chamadas repetidas
- `WritePipelineResult` está em `write/pipeline.py` (linhas 28-40), `WriteResult` em `client.py` (linhas 17-30)
- `UpsertResult` em `write/upsert.py` tem `confirmed: int` e `deleted: int` — precisam virar listas
- O pipeline segue o pattern de savepoints — cada operação usa `session.begin_nested()`
- Testes usam FakeLLMProvider sem DB — ajustes serão em assertions, não em mocks

## Open Questions

None.
