# Audit Report: Write Pipeline DX Fixes

> Audited on 2026-03-19
> Spec: `.vibeflow/specs/write-pipeline-dx-fixes.md`

**Verdict: PASS**

## Tests

- **ruff check**: All checks passed
- **ruff format**: 70 files already formatted
- **mypy**: Success — no issues found in 41 source files
- **pytest**: 330 passed, 0 failed (8 warnings — pre-existing RuntimeWarning from mock coroutines)

## DoD Checklist

- [x] **DoD #1 — Entity upsert pós-resolve**: `create_or_update_entity()` é chamado para TODA entity resolvida (não apenas novas) no step 3b do pipeline.
  - Evidence: `src/arandu/write/pipeline.py:173-188` — loop `for resolved in entity_map.values()` com try/except fail-safe
  - Test: `tests/test_write_pipeline.py::TestWritePipeline::test_entity_upsert_called_for_all_resolved_entities` — verifica `mock_entity_upsert.call_count >= 2` e que `user_id` é passado corretamente

- [x] **DoD #2 — `facts_unchanged` é `list`**: Ambos `WritePipelineResult` e `WriteResult` usam `list = field(default_factory=list)`.
  - Evidence: `src/arandu/write/pipeline.py:36` (`facts_unchanged: list`), `src/arandu/client.py:24` (`facts_unchanged: list`)
  - Test: `tests/test_write_pipeline.py::TestUpsert::test_execute_upsert_noop_returns_list` — verifica que `result.confirmed` é lista com dict contendo `fact_text`, `subject`, `matched_fact_id`

- [x] **DoD #3 — `facts_deleted` é `list`**: Mesma mudança para `facts_deleted`.
  - Evidence: `src/arandu/write/pipeline.py:37` (`facts_deleted: list`), `src/arandu/client.py:25` (`facts_deleted: list`)
  - Test: `tests/test_write_pipeline.py::TestUpsert::test_execute_upsert_delete_returns_list` — verifica que `result.deleted` é lista com dict

- [x] **DoD #4 — `UpsertResult` atualizado**: `confirmed` e `deleted` mudaram de `int` para `list`.
  - Evidence: `src/arandu/write/upsert.py:33-34` — `confirmed: list = field(default_factory=list)`, `deleted: list = field(default_factory=list)`
  - NOOP branch (`upsert.py:403-411`): `result.confirmed.append({...})`
  - DELETE branch (`upsert.py:413-421`): `result.deleted.append({...})`
  - Pipeline consome com `len()`: `pipeline.py:241` (`len(upsert_result.confirmed)`), `pipeline.py:242` (`len(upsert_result.deleted)`)

- [x] **DoD #5 — Testes existentes ajustados**: Todos os testes que comparavam com `int` foram atualizados.
  - `tests/test_write_pipeline.py:565` — `len(result.facts_unchanged) == 0`
  - `tests/test_sdk_integration.py` — `isinstance(result.facts_unchanged, list)`, `facts_unchanged=[]`
  - `tests/test_sdk_adapter_contracts.py` — stub updated, test renamed and checks list type
  - `tests/test_full_integration.py:138-139` — `r.confirmed == []`, `r.deleted == []`

## Pattern Compliance

- [x] **Pipeline Orchestration** — Entity upsert adicionado como step 3b dentro do savepoint existente (`session.begin_nested()`), após `resolve_entities()`. Não commita — caller gerencia transação. Segue pattern exato.
  - Evidence: `pipeline.py:141` (savepoint), `pipeline.py:173-188` (entity upsert loop dentro do savepoint)

- [x] **Data Access** — Usa `create_or_update_entity()` que é ON CONFLICT DO UPDATE (idempotente). Fail-safe com try/except + `logger.warning()`.
  - Evidence: `pipeline.py:183-188` — `except Exception as e: logger.warning(...)`

- [x] **Testing** — Testes usam mock session, FakeLLMProvider, FakeEmbeddingProvider, `run_async()`. Sem DB. `patch()` para verificar chamadas ao `create_or_update_entity`.
  - Evidence: `test_write_pipeline.py:541-588` (entity upsert test)

## Convention Violations

Nenhuma.

## Budget

Spec: ≤ 6 files. Actual: 9 files modified (3 acima do budget).

Arquivos extras: `tests/test_sdk_integration.py`, `tests/test_sdk_adapter_contracts.py`, `tests/test_full_integration.py` — necessários para manter testes passando após a breaking change de `int` → `list`. Budget excedido justificadamente para manter DoD #5 (testes existentes ajustados).

## Docs

- [x] `docs/en/concepts/write-pipeline.md:309-310` — `facts_unchanged` e `facts_deleted` agora documentados como "List of confirmed/retracted facts"
- [x] `docs/pt-BR/concepts/write-pipeline.md:309-310` — Mesma atualização em português
