## Findings ### High 1. **Mehrere gültige Bewertungen desselben Kontos/Stichtags lassen sich nicht bestätigen** **Datei:** `src/jarvis_finance/services/portfolio_data.py:162-181, 191-196, 229-243, 506-510` `_valuation_disposition()` bestimmt die nächste Version ausschließlich anhand bereits persistierter kanonischer Snapshots. Mehrere unterschiedliche Quellzeilen für dasselbe Konto und Datum innerhalb derselben Preview sehen daher denselben Ausgangszustand und erhalten alle Version `1` beziehungsweise alle `N+1`. Beim Confirm kollidieren sie mit `UNIQUE(scope_kind, scope_id, valuation_at, snapshot_version)` und der gesamte atomare Batch wird zurückgerollt. Ein read-only In-Memory-Gegenbeispiel mit zwei zulässigen `account_value_snapshots` desselben Tages ergab Preview `new=2`, anschließend `ValueError: Ingestion konnte nicht atomar bestätigt werden`, null Snapshots und null Batches. Die Quelltabellen erlauben diesen Zustand ausdrücklich, da sie nur einen Index und keine Eindeutigkeit auf Konto/Datum besitzen. 2. **Stichtagsfremde Daten können als vollständig „matched“ ausgewiesen werden** **Datei:** `src/jarvis_finance/services/portfolio_data.py:670-734, 753-803, 827-836` Die Engine lädt jeweils den letzten Positionssnapshot, Preis und FX-Kurs mit Datum `<= as_of`. Ein alter Positionssnapshot fügt zwar `stale_snapshot` hinzu, beeinflusst aber weder Mengenstatus noch Gesamtstatus. Sind alter Positionssnapshot, Preis, FX, Cash und Kontototal untereinander datumsgleich, werden Mengen-, Wert- und Accountvergleich als `matched` bewertet; der Overall-Status wird allein aus diesen Statuswerten gebildet und bleibt ebenfalls `matched`. Ein synthetischer Gegencheck für `as_of=2026-07-23` mit ausschließlich Daten vom `2025-12-31` ergab: `overall matched`, `quantity matched`, `valuation matched`, `account matched`, lediglich `reason_codes=['stale_snapshot']`. Das widerspricht dem dokumentierten Vertrag, wonach stichtagsfremde Preise/Snapshots eine Lücke beziehungsweise Nichtvergleichbarkeit bleiben müssen. ### Medium 3. **Die Preview-TTL wird vom Client kontrolliert und Idempotenz endet nach Ablauf** **Datei:** `src/jarvis_finance/services/portfolio_data.py:451-492` `preview_created_at` stammt unverändert aus dem Confirm-Request und ist weder serverseitig gespeichert noch kryptografisch an Preview/Confirmation gebunden. `preview_id` und `payload_hash` enthalten diesen Zeitpunkt nicht. Ein Client kann deshalb eine abgelaufene Preview durch Ersetzen von `preview_created_at` mit der aktuellen oder einer zukünftigen Zeit bestätigen. Der Gegencheck lehnte denselben Payload zunächst als abgelaufen ab und akzeptierte ihn nach alleiniger Änderung dieses Feldes. Zusätzlich erfolgt die Ablaufprüfung vor der Suche nach einem bereits bestätigten `confirmation_id`. Ein legitimer Retry desselben erfolgreichen Confirms ist nach 15 Minuten daher nicht mehr idempotent, sondern schlägt als abgelaufen fehl. 4. **Nicht bestätigte beziehungsweise voidierte Transaktionen gelangen in Sprint-6-Projektionen** **Datei:** `src/jarvis_finance/services/portfolio_data.py:323-327, 344-360, 646-669` Die kanonische Ingestion filtert weder `COALESCE(is_voided,0)=0` noch `COALESCE(is_confirmed,1)=1`; voidierte oder explizit unbestätigte Ledgerzeilen werden daher als normale `unchanged` Activities in Preview, Counts und Audit-Lineage aufgenommen. Die Reconciliation entfernt zwar voidierte Zeilen, berücksichtigt aber weiterhin `is_confirmed=0` bei Mengen, Activity-Coverage und Cost-Basis-Coverage. Ein unbestätigter Kauf kann dadurch einen Mengen-Mismatch oder scheinbaren Match erzeugen. Bestehende Ledger-/Reconciliation-Pfade im Projekt filtern bestätigte und nicht voidierte Transaktionen ausdrücklich. ## Abschluss - Vollständigen uncommitted Sprint-6-Stand gegen `480d7a1b72a950e864c4bb021d3f58af8f8c15f9` read-only geprüft, einschließlich untracked Backend-, Test- und Vue-Dateien. - **4 sprintrelevante Findings:** 2 High, 2 Medium. - Keine Dateien erstellt oder verändert; Git-Status blieb unverändert, `git diff --check` blieb grün. - Keine Commits, Pushes, Deployments oder produktiven Datenzugriffe. - Nicht blockierendes Umgebungsdetail: Im Worktree existierte kein `.venv`; isolierte Gegenbeispiele liefen deshalb mit System-Python, `PYTHONPATH=src` und ausschließlich In-Memory-SQLite.