BLOCK — P0=0 P1=11 ### P1 1. **Read-only Preview ist im Standardmodus gesperrt** — `src/jarvis_finance/api/main.py:23-38,112-116` Die neue manuelle Preview-Route fehlt in `READ_ONLY_POST_PATHS` und erhält bei deaktivierten Writes `403`. **Minimalfix:** Exakte Preview-Route allowlisten und API-Test für `write_mode=disabled`, Route-Existenz und No-write ergänzen. 2. **Cash-/Known-Wealth-Summen ignorieren bereits bestätigte Bewegungen** — `src/jarvis_finance/services/raiffeisen_manual_snapshot.py:99-106,225-235` Der vorherige Kontowert stammt nur aus dem letzten Snapshot; bestätigte Bewegungen zwischen diesem und dem neuen Stichtag fehlen. Dadurch können Preview, Confirmation-Lineage und tatsächliches Read-Model auseinanderlaufen. **Minimalfix:** Vorwert über `effective_cash_evidence(..., as_of=snapshot_date)` bestimmen und Bank-/Known-Wealth-Summen aus derselben kanonischen Baseline ableiten. 3. **Append-only-Guards sind per SQLite `INSERT OR REPLACE` umgehbar** — `src/jarvis_finance/storage/migrations.py:2859-2880` UPDATE-/DELETE-Trigger verhindern bei deaktivierten rekursiven Triggern keine implizite Löschung und Neuanlage durch `REPLACE`. **Minimalfix:** `REPLACE` schemafest verhindern, etwa durch immutable Child-/Lineage-Tabellen ohne ersetzbare Konfliktidentität; zusätzlich adversarialen `INSERT OR REPLACE`-Test aufnehmen. 4. **Portfolioanalyse zählt True-Wealth-Positionen doppelt** — `src/jarvis_finance/services/portfolio_analysis_v1.py:172-183,209-216` Alle Positionen des kanonischen Analyse-Snapshots werden Aktien/ETF zugerechnet; anschließend wird der aggregierte True-Wealth-Wert nochmals addiert. Allokation, Konzentration und Policy-Abweichung werden dadurch materiell zu hoch. **Minimalfix:** Positionen anhand kanonischer Account-Lineage trennen und True-Wealth-Positionen vor direkter Aktien-/ETF- und Konzentrationsaggregation ausschließen. 5. **YTD liefert eine Einjahresreihe** — `src/jarvis_finance/services/wealth_cockpit.py:1219-1227` Obwohl `modelled_wealth` YTD unterstützt, mappt das Cockpit `ytd` auf `1y`; der sichtbare YTD-Filter zeigt damit den falschen Zeitraum. **Minimalfix:** `ytd` direkt an `build_modelled_wealth_development` weiterreichen und Periodengrenzen vertraglich testen. 6. **Chart stellt unbestätigte Historie als bestätigt dar** — `frontend/src/components/wealth/WealthDevelopmentChart.vue:18-21,75-87`; `frontend/src/components/wealth/WealthCockpitPanel.vue:47` Ohne bestätigten Anker wird die gesamte modellierte Reihe durchgezogen, während die Legende sie als bestätigte Historie bezeichnet. Zudem werden auch manuelle Komponenten-Korrekturen als „neuer bestätigter Anker“ beschriftet, obwohl sie die Ankergrenze bewusst nicht verschieben. **Minimalfix:** Ankerlose Reihen vollständig als modelliert/gestrichelt darstellen, Legende zustandsabhängig machen und Komponenten-Korrekturen ausdrücklich nicht als Haushaltsanker bezeichnen. 7. **Refresh ist nicht stale-only** — `src/jarvis_finance/services/asset_price_refresh.py:93-105,126-189` `stale_before` wird nur gespeichert, aber keinem Runner übergeben. Aktien verwenden eine unabhängige Business-Day-Regel, FX fest 24 Stunden und Krypto aktualisiert sämtliche aktiven Bestände. **Minimalfix:** Den Job-Cutoff an alle drei Runner binden, ausschließlich cutoff-stale Kandidaten auswählen und `stale_candidates` aus genau dieser Auswahl bilden. 8. **Asset-Refresh verändert Transaktionen und kann diese Änderung nicht zurückrollen** — `src/jarvis_finance/services/asset_price_refresh.py:164-189,249-250`; `src/jarvis_finance/services/market_service.py:542-583` Der FX-Runner ruft eine Funktion auf, die `transactions` aktualisiert und selbst committet. Der spätere Fingerprint-Guard meldet nur den Verstoß; die verbotene Mutation ist bereits dauerhaft. **Minimalfix:** Einen preis-/FX-tabellenbeschränkten Refresh-Pfad ohne Transaction-DML verwenden; verbotene Tabellen zusätzlich vor dem Commit technisch blockieren. 9. **Doppelte Worker-Ausführung ist nicht atomar ausgeschlossen** — `src/jarvis_finance/services/asset_price_refresh.py:209-213` Zwei Worker können gleichzeitig `queued` lesen, beide auf `running` setzen und Provider sowie Snapshot-Erzeugung doppelt ausführen. Der Unique-Index schützt nur Jobstarts, nicht Job-Claims. **Minimalfix:** Job unter `BEGIN IMMEDIATE` mit bedingtem `UPDATE … WHERE status='queued'` claimen und nur bei genau einer geänderten Zeile ausführen. 10. **Runtime-Status ermöglicht serverseitige Requests zu einem caller-kontrollierten Ziel** — `src/jarvis_finance/api/routers/system.py:15-20`; `src/jarvis_finance/services/system_ops.py:102-108` `Origin` beziehungsweise `Referer` wird als Frontend-URL übernommen und anschließend serverseitig per Healthcheck aufgerufen. CORS verhindert direkte HTTP-Aufrufe mit beliebigem Header nicht. **Minimalfix:** Niemals clientgelieferte Origins abrufen; Frontend-Ziel ausschließlich aus validierter Runtime-Konfiguration beziehungsweise enger Allowlist beziehen. 11. **Schema-52-Migration repariert bereits angelegte Zwischenstände nicht** — `src/jarvis_finance/storage/migrations.py:2840-2858,2934-2975` `CREATE TABLE IF NOT EXISTS` ergänzt insbesondere das nachträglich erforderliche `payload_hash` nicht, wenn eine frühere Schema-52-Fassung die Tabelle bereits angelegt hat. Replay bleibt dann formal erfolgreich, der Confirm-Pfad scheitert jedoch am fehlenden Feld. **Minimalfix:** Vorhandene Tabellen per `PRAGMA table_info` prüfen und fehlende additive Spalten/Indizes/Trigger idempotent nachrüsten; Zwischenstand→aktuell zweimal migrieren und prüfen. ### P2 - `tests/unit/test_asset_price_refresh.py:21-105` — Keine Gegenproben für Cutoff-Staleness, verbotene Transaction-Mutation oder konkurrierende Worker-Claims. - `tests/unit/test_schema.py:18-21` — Schema-Gate erhöht nur die Versionszahl; erforderliche Schema-52-Spalten sowie Zwischenstand-/Replay-Upgrade werden nicht geprüft. - `.github/workflows/portfolio-phase3-integration.yml:171-188` — Der fokussierte CI-Vertrag führt Service-Tests aus, aber keine exakten HTTP-Vertragstests für die neuen Preview-/Confirm-/Refresh-Routen im deaktivierten und lokalen Write-Modus.