# Sprint 4 / 4.1 – Portfolio Policy Foundation & Contract Hardening

## Fachlicher Zweck

Sprint 4 führt eine versionierte Portfolio-Policy als kontrollierte, lokale Entscheidungsgrundlage ein. Sprint 4.1 härtet den Vertrag für request-basierte Idempotenz, unveränderliche historische Versionen, Decimal-Validierung, minimale Audits und eine read-only Detailansicht. Die Policy löst keine Orders, Rebalancings, Anlageempfehlungen oder Performanceberechnungen aus.

## Daten- und API-Vertrag

- Preview ist vollständig read-only und liefert einen inhaltsgebundenen `preview_id` sowie einen neuen serverseitigen `confirmation_id`.
- Confirm verlangt exakt den dargestellten Policy-Payload, `preview_id`, `confirmation_id` und `confirm=true`.
- Gleiche `confirmation_id` plus gleicher kanonischer Payload ist idempotent und liefert dieselbe Policy-Version.
- Gleiche `confirmation_id` plus abweichender Preview oder Payload wird fail-closed abgelehnt.
- Eine neue `confirmation_id` erzeugt auch bei früher bereits bestätigtem Inhalt eine neue Version; A → B → A ergibt Version 1 → 2 → 3 mit korrekter Vorgängerkette.
- Es ist genau eine Version aktiv. Bestätigte Policy-Inhalte und Allokationen bleiben unveränderlich.
- `GET /api/portfolio/policy/{policy_id}` liefert aktive und historische Versionen mit ihren eigenen Allokationen. Unbekannte IDs ergeben 404 ohne ID-Leak. Der GET-Pfad verändert keine Policy-, Allokations-, Audit- oder Zeitstempeldaten.
- Das Confirm-Audit enthält als Detail-Allowlist ausschließlich `version` und `previous_policy_id`; `policy_id` bleibt `entity_id`.
- Geld- und Prozentwerte werden Decimal-basiert geprüft. Binäre Floats, negative Werte, nicht-endliche Werte, Prozentwerte über 100, ungültige Bandgrenzen, Allokationssummen ungleich 100, Crypto-Limitverletzungen und nicht unterstützte Benchmark-Gewichte werden fail-closed abgelehnt.

## Frontend-Vertrag

- Das Frontend übernimmt beide Kennungen unverändert aus der Preview und bestätigt eine unveränderliche Kopie des dargestellten Payloads.
- Eine fachliche Änderung nach Preview invalidiert Preview und Bestätigungskennung; vor Confirm ist eine neue Preview erforderlich.
- Während Confirm sind Mehrfachauslösung und Doppelklick gesperrt. Ein technisch wiederholter identischer Request bleibt serverseitig idempotent.
- Aktive und historische Versionen sind über dieselbe read-only Detailansicht erreichbar.
- Die Detailansicht zeigt vollständige Policy- und Allokationswerte und ist eindeutig als `Nur lesen · Schreibgeschützt` markiert. Sie enthält keine Speicher-, Aktivierungs-, Bearbeitungs-, Kopier-, Order- oder Rebalancing-Aktion.
- Konflikt-, Validierungs-, 404- und allgemeine Ladefehler werden auf Deutsch dargestellt; Kennungen, Hashes und Audit-IDs erscheinen nicht in Benutzermeldungen oder Browserlogs.

## Migration

Die additive Kompatibilitätsmigration ergänzt `portfolio_policies` um nullable `confirmation_id` und `payload_hash`, einen partiellen Unique-Index auf vorhandenen Bestätigungskennungen und einen Trigger gegen nachträgliche Änderungen der Request-Identität. Bestehende Sprint-4-Zeilen bleiben ohne Backfill lesbar. Die Migration ist sowohl auf einer leeren Datenbank als auch auf einem synthetischen Sprint-4-Bestand getestet.

## Sicherheitsgrenzen

- Runtime-Daten und UAT-Daten liegen außerhalb des Repositories; Tests und UAT verwenden ausschließlich synthetische Daten.
- Die bestehende lokale GET-Konvention und die vorhandene Write-Security bleiben unverändert. Im deaktivierten Write-Modus werden entfernte Schreibzugriffe mit 403 abgelehnt; Sprint 4.1 führt keine zweite Auth-/API-Konvention ein.
- Preview persistiert nichts. Confirm bleibt Preview → explizites Confirm → Audit.
- Historische Details sind read-only. Es existiert kein Reaktivierungs- oder Schreibpfad.
- Policy-Evaluation bleibt `not_assessable`, solange aktuelle Werte nicht vollständig klassifiziert und vergleichbar sind. Keine FX-, Performance- oder Empfehlungsaussage.

## Validation und Abschlussgates

- Vollständige Python-Suite: **618 passed**.
- Vollständige Frontend-Suite: **42 Testdateien / 145 Tests passed**.
- Nach dem UAT-Fix fokussierte Policy-/Portfolio-Frontendtests: **4 Testdateien / 17 Tests passed**.
- Frontend-Typecheck: grün.
- Production Build: grün, 414 Module transformiert; bestehende nicht blockierende Chunk-Warnung >500 kB.
- Ruff, Python Compile und `git diff --check`: grün.
- Migration-, OpenAPI-, Detail-GET-, Idempotenz-, Audit-, Auth-/Write-Security- und Git-Safety-Tests: grün.

## Responsive-UAT

UAT erfolgte am 2026-07-23 gegen eine isolierte synthetische SQLite-Runtime mit lokalem FastAPI-/Vue-Stack:

- Desktop: Preview → Änderung → erzwungene neue Preview → Confirm; synchrone Doppelbestätigung erzeugte nur eine Version und einen Audit-Eintrag.
- Anschließend wurde eine zweite Version bestätigt; Version 2 war aktiv, Version 1 archiviert, genau eine Version aktiv.
- Aktive und historische Details wurden geöffnet. Beide zeigten vollständige versionsrichtige Werte; die historische Ansicht war eindeutig schreibgeschützt und enthielt als einzige Aktion `Ansicht schliessen`.
- iPad quer, 1024×768: keine horizontale Seitenüberbreite, keine Überlappung; History und Detailbuttons erreichbar.
- Mobile, 390×844: keine horizontale Seitenüberbreite; History und Detailansicht bedienbar. Ein im Abschlussreview erkannter zu enger Allokationstabellenkopf wurde durch mobile Allokationskarten ersetzt und erneut visuell geprüft.
- Deutsche Fehlerzustände wurden für veraltete Preview, Confirm-Konflikt, Detail-404 und allgemeine Ladefehler geprüft.

Synthetischer UAT-Endstand: 2 Policy-Versionen, 1 aktive Version, 2 Policy-Audits und 8 versionsgebundene Allokationszeilen.

## Abschließender Sol-Review

Genau ein read-only Abschlussreview des gesamten Sprint-Diffs wurde nach den vollständigen Suites durchgeführt. Ergebnis:

- keine Critical- oder High-Findings;
- ein berechtigtes responsives Finding: die vier Spalten der read-only Allokationstabelle waren bei 390 Pixeln zwar scrollbar, aber visuell zu eng;
- Korrektur innerhalb des Sprint-Scopes: mobile Allokationskarten, Desktop/iPad behalten die Tabelle;
- fokussierte Frontendtests, Typecheck, Production Build und Mobile-UAT danach erneut grün;
- größere Folgearbeiten wurden nicht in Sprint 4.1 gezogen.

## Rollback-Hinweis

Code-Rollback erfolgt durch Revert des Sprint-4.1-Commits und erneutes Ausrollen des vorherigen Builds. Die additiven Spalten, der Index und die Trigger sollen nicht automatisch gedroppt werden; bestehende historische Policies bleiben absichtlich erhalten. Vor einem Schema-Rückbau ist ein Runtime-Backup erforderlich. Der Sprint-4-Code kann nullable Zusatzspalten ignorieren, besitzt aber nicht den gehärteten request-basierten Idempotenzvertrag; deshalb sind während eines Rollbacks Policy-Schreibzugriffe vorzugsweise zu deaktivieren. Ein fachlicher Rollback einer bestätigten Policy erfolgt nicht durch Mutation alter Zeilen, sondern ausschließlich durch eine neue bestätigte Version.

## Offene Phase-3-Lücke

Die Policy-Foundation ist abgeschlossen. Weiterhin fehlt eine reproduzierbare Portfolio-Performance-Basis: kanonische Investment Activities, Gebühren-/Steuer- und Cost-Basis-Semantik, bewertbare FX-/Valuation-Snapshots sowie kontrollgerechnete TWR-/MWR-Ergebnisse mit Zeitraum, Methode, Quelle und Quality-Status. Bis diese Grundlage existiert, bleiben Performance- und Rebalancing-Aussagen außerhalb des freigegebenen Funktionsumfangs.
