P0: 0 P1: 5 P2: 2 P1 — `src/jarvis_finance/services/raiffeisen_manual_snapshot.py:323-341`, `src/jarvis_finance/storage/migrations.py:2844-2857` — Confirm prüft Replay und Baseline vor Beginn der Schreibtransaktion; außerdem wird ein Preview nur anhand frei konstruierbarer Präfixe validiert. Zwei konkurrierende Confirm-Aufrufe können daher dieselbe Baseline passieren und anschließend doppelte Snapshots beziehungsweise einen ungefangenen Unique-Fehler erzeugen. `input_fingerprint` und `payload_hash` sind in der Datenbank nicht als gemeinsame Idempotenzidentität eindeutig. Minimaler Fix: Preview unveränderlich persistieren oder signieren, Ablaufzeit prüfen, Confirm mit `BEGIN IMMEDIATE` starten und Replay-, Payload- und Baseline-Prüfung unter derselben Sperre ausführen; zusätzlich eine Unique-Constraint für die kanonische Payload-/Baseline-Identität und konfliktfestes Replay-Handling ergänzen. P1 — `src/jarvis_finance/services/raiffeisen_manual_snapshot.py:378-424`, `src/jarvis_finance/storage/migrations.py:2859-2864` — Nur der Confirmation-Datensatz ist unveränderlich; die zwei erzeugten `cash_account_snapshots` und der Mitgliedschafts-Snapshot bleiben update- und löschbar. Eine synthetische Gegenprobe konnte den Cash-Wert auf `999.00` ändern und den Mitgliedschafts-Snapshot löschen, während die Confirmation bestehen blieb. Damit können Acceptance-Summen und Audit-Lineage nachträglich auseinanderlaufen; ein Replay meldet trotzdem `already_applied`. Minimaler Fix: UPDATE/DELETE-Trigger für `source='manual_screenshot_snapshot'` beziehungsweise `source_type='manual_screenshot_snapshot'` ergänzen und deren Unveränderlichkeit im 51→52-Migrationsgate testen. P1 — `src/jarvis_finance/services/asset_price_refresh.py:118-181` — Der Job respektiert seinen gespeicherten `stale_before`-Vertrag nicht: Equity zählt alle geeigneten Instrumente und nutzt eine separate Business-Day-Heuristik, Crypto aktualisiert alle gehaltenen Assets, und FX verwendet fest 24 Stunden. Ein angeforderter Schwellenwert von beispielsweise 12 oder 720 Stunden ändert diese Selektion nicht; frische Kurse können Provideraufrufe auslösen und `stale_candidates` ist sachlich falsch. Minimaler Fix: `stale_before` an alle Source-Runner übergeben und Kandidaten ausschließlich anhand gespeicherter Preis-/FX-Zeitstempel vor diesem Cutoff auswählen. P1 — `src/jarvis_finance/services/modelled_wealth.py:708-731`, `frontend/src/components/wealth/WealthDevelopmentChart.vue:71-93`, `frontend/src/components/wealth/WealthCockpitPanel.vue:47` — `has_confirmed_anchor` wird durch jede am Tag bestätigte Komponente gesetzt, einschließlich Cash- und Mitgliedschaftskorrekturen, obwohl `last_confirmed_anchor_date` dadurch ausdrücklich nicht verschoben wird. Die Gegenprobe ergab bei einer reinen Cash-Korrektur `last_confirmed_anchor_date=None`, aber `has_confirmed_anchor=True`; UI, Tooltip und Tabelle bezeichnen sie deshalb als „Bestätigter Snapshot / Importanker“, und die Marker-Liste sogar als „neuer bestätigter Anker“. Minimaler Fix: Haushaltsanker und Komponentenbestätigung getrennt modellieren; `has_confirmed_anchor` ausschließlich aus dem kanonischen Haushaltsanker ableiten und Korrekturen überall nur als Korrektur markieren. P1 — `src/jarvis_finance/services/portfolio_analysis_v1.py:186-207`, `src/jarvis_finance/services/portfolio_analysis_v1.py:229-240` — Aktien, ETF und True Wealth erhalten absichtlich keine Einzel-Policy, obwohl die aktive Policy sie über `equity_policy_group` abdeckt. Sie werden dadurch stets als `unavailable` klassifiziert, machen selbst eine vollständig bewertete Analyse global `partial` und erzeugen irreführende „Daten ergänzen“-Hinweise. Das ist keine ehrliche Missingness, sondern verwechselt „keine separate Policy vorgesehen“ mit „Daten fehlen“. Minimaler Fix: einen `not_applicable`/rein informativen Status für die drei Detailzeilen einführen, nur den kanonischen Equity-Policy-Block für Policy-Completeness bewerten und solche Zeilen aus Missingness und Hinweisen ausschließen. P2 — `src/jarvis_finance/services/asset_price_refresh.py:93-114`, `src/jarvis_finance/storage/migrations.py:2865-2896` — „Kein laufender Job“ wird per ungeschütztem SELECT geprüft; die Migration besitzt keine partielle Unique-Constraint für `queued`/`running`. Parallele Startanfragen können deshalb zwei Provider-Jobs anlegen oder mit einem ungefangenen SQLite-Lockfehler enden statt deterministisch HTTP 409 zu liefern. Minimaler Fix: Start unter `BEGIN IMMEDIATE` ausführen und zusätzlich einen datenbankseitigen Unique-Guard für höchstens einen aktiven Job anlegen. P2 — `scripts/ci_portfolio_phase3_gate.py:164-214`, `.github/workflows/portfolio-phase3-integration.yml:169-170` — Der CI-Migrationsjob prüft weiterhin nur einen historischen Schema-40-Fixture gegen 52; ein expliziter 51→52-Upgrade mit Sprint-23-Constraints, Triggern und zweitem No-op-Lauf fehlt. Damit ist der unmittelbar produktionsrelevante Upgradepfad nicht durch den Workflow abgesichert. Minimaler Fix: einen echten Schema-51-Fixture erzeugen und 51→52, wiederholte Anwendung, Foreign Keys, CHECK-/Unique-Constraints sowie Immutable-Trigger im CI-Gate verifizieren. VERDICT: CHANGES REQUIRED — staged tree `f0343a9ea57b4ce8c29d86f2015bb9426d74e57d`