# Phase 13.4 — Historical Data Trust Gate (Provenance + Sanity Validation) **Status:** FINAL CLOSED (technisch verifiziert, dokumentiert) **Datum:** 24.08.2026 **Autor:** Rain Ocampo (Hermes) **Freigabe:** Christian List (Phase 13.4 Finalisierung / Second-Brain-Sync) --- ## 1. Ausgangsproblem (aus Phase 13.3) Die Phase-13.3-Forensik zeigte: **HTTP 200 + erfolgreiches Parsing bedeutet NICHT automatisch vertrauenswürdige Daten.** Die bekannten problematischen November-Tage (11-09, 11-10, 11-12) wurden von Dukascopy mit HTTP 200 geliefert, technisch valide geparst, aber enthielten **fachlich unplausible Daten** (falsches Preisniveau, Wochenendaktivität). Der `source_timestamp` war in allen Fällen NULL. **Kernbefund:** Ein reiner "Download erfolgreich + Parse erfolgreich"-Check ist unzureichend. Es fehlt eine Schutzschicht, die Provenance und fachliche Plausibilität vor der Persistierung validiert. --- ## 2. Neue Pipeline-Reihenfolge ``` RAW DOWNLOAD → PROVENANCE CAPTURE → TECHNICAL PARSE → SANITY VALIDATION → TRUST DECISION → SESSION → TECHNICAL/MARKET QUALITY → EFFECTIVE ELIGIBILITY → GAP → PERSIST RAW → DERIVED → DATASET VERSION ``` Das Trust Gate sitzt **nach** dem technischen Parse und **vor** der Persistierung (Schritt 4c in `backfill.py`). Es klassifiziert, es löscht nicht. --- ## 3. Provenance-Modell `HistoricalProvenanceContext` (Version `provenance_v1`) — 16 Felder: | Feld | Semantik | |------|----------| | `provider` | Datenquelle (Dukascopy, OANDA, FXCM, Polygon, …) | | `instrument` | Instrument (EURUSD, …) | | `timeframe` | Zeitrahmen (M1, M5, …) | | `source_url` | Quelle (Credentials redacted) | | `http_status` | HTTP-Statuscode | | `content_length` | Rohdatenlänge | | `raw_file_sha256` | SHA-256 der Rohdatei | | `retrieved_at` | Abrufzeitpunkt (volatile, nicht im Hash) | | `source_timestamp` | Quell-Zeitstempel (kritisch, fail-closed) | | `adapter_version` | Adapter-Version | | `parser_version` | Parser-Version | | `normalization_version` | Normalisierungs-Version | | `code_version` | Code-/Commit-Version | | `raw_source` | Rohquelle | | `dataset_identity` | Dataset-Identität | **Keine Secrets in `source_url`** — `redact_url()` entfernt Credentials; `to_dict()` wendet Redaction an. --- ## 4. Trust States | State | Bedeutung | Persistierbar? | |-------|-----------|----------------| | `TRUSTED` | Provenance vollständig, Sanity plausibel | ✅ Ja | | `CONDITIONAL` | Warnende Hinweise (z. B. Wochenendaktivität) | ⚠️ Nur mit expliziter Policy | | `UNTRUSTED` | Fachlich unplausible Daten | ❌ Nein | | `UNKNOWN` | Kritische Provenance fehlt | ❌ Nein (fail-closed) | Version: `trust_v1` --- ## 5. Reason Codes (20, erweiterbar) `PRICE_LEVEL_MISMATCH`, `WEEKEND_DATA_ANOMALY`, `STALE_DAY`, `ZERO_VOLUME_DAY`, `SOURCE_TIMESTAMP_MISSING`, `RAW_ARTIFACT_MISSING`, `PROVENANCE_INCOMPLETE`, `CROSS_PROVIDER_MISMATCH`, `NAN_OR_INF`, `INSTRUMENT_MISMATCH`, `TIMEFRAME_MISMATCH`, `TIMESTAMP_DUPLICATE`, `TIMESTAMP_MISSING_MINUTE`, `TIMESTAMP_OUT_OF_RANGE`, `TIMESTAMP_NON_MONOTONIC`, `REFERENCE_UNAVAILABLE`, `PROVIDER_OUTAGE`, `PROVIDER_DATA_ERROR` --- ## 6. Provider-Outage vs Provider-Data-Error **Strikt getrennte Codepfade:** - **`PROVIDER_OUTAGE`** — technischer Ausfall: HTTP 404/503, Timeout, Connection-Failure - **`PROVIDER_DATA_ERROR`** — HTTP 200, technisch valide, aber fachlich unplausible Daten Diese Zustände bekommen **nie denselben Status/Codepfad**. --- ## 7. Sanity-Prüfungen (6 Klassen, providerneutral) | Klasse | Prüfung | Konfigurierbar | |--------|---------|----------------| | A | Price-Level (gegen unabhängige Referenz) | ✅ asset-class-aware | | B | Weekend/Session-Anomalie | ✅ | | C | Stale-Day (flacher Tag) | ✅ | | D | Volume (Zero-Volume-Day) | ✅ | | E | Cross-Provider (optional, nur Validierung) | ✅ | | F | Timestamp (UTC, Monotonie, Duplikate, Range) | ✅ | **Keine universelle 1,5%-Regel.** Die Forex-Default-Toleranz wurde aus dem Phase-13.3-Befund auf **0,5%** kalibriert (die bekannten falschen Tage wichen nur 0,55–1,07% ab und wurden bei 1,5% fälschlich TRUSTED). Schwellen sind versioniert und konfigurierbar. --- ## 8. Fail-Closed-Verhalten - **TRUSTED:** Persistierung zulässig - **CONDITIONAL:** nur mit expliziter Policy (Audit bleibt erhalten) - **UNTRUSTED:** nicht als normaler vertrauenswürdiger Historical-Datensatz persistieren - **UNKNOWN:** fail-closed, sofern kritische Provenance fehlt --- ## 9. Raw-Artifact-Retention - Content-Hash (SHA-256) + kontrollierter Artifact Store - Keine unkontrollierte Datenexplosion - Retention-Policy: Hash + Metadaten (Größe, Retrieval-Zeit, Provider, Instrument, Zeitraum, HTTP-Status, URL ohne Credentials) --- ## 10. source_timestamp-Regel - **Semantik:** Quell-Zeitstempel des Datenpunkts (UTC) - **Pflichtgrad:** kritisch - **Range vs Einzelwert:** beides unterstützt - **UTC:** zwingend - **Fail-Closed:** fehlt `source_timestamp` → `SOURCE_TIMESTAMP_MISSING` → `UNKNOWN` (nicht persistierbar) - **Keine Altbestandsmigration** (November-Daten bleiben unverändert) --- ## 11. Hash / Reproduzierbarkeit - `provenance_hash`: deterministischer SHA-256 über die Provenance - **Volatile `retrieved_at` wird NICHT in den deterministischen Hash aufgenommen** - Gleiche Raw-Datei + gleiche Modelle + gleiche Konfiguration → gleicher Hash - Andere Raw-Datei / andere Trust-Policy → unterscheidbar --- ## 12. Ergebnisse - **Trust-Gate-Tests: 27/27 grün** - **Phase-13.3-Reproduction: 9/9 grün** - 11-09 (Sa): UNTRUSTED (PRICE_LEVEL_MISMATCH + WEEKEND_DATA_ANOMALY) - 11-10 (So): CONDITIONAL (WEEKEND_DATA_ANOMALY) - 11-12 (Di): UNTRUSTED (PRICE_LEVEL_MISMATCH) - **Keiner TRUSTED** — Reason Codes folgen aus den Daten, nicht gefittet - **Relevante Phase-13-Regressionen grün** (Provider-Failures, Ingestion-State, Idempotenz, Hash-Konsistenz, Daily-Report) --- ## 13. Deploy / Health-Status - Container `historical-service`: **Up** - `/health`: `{"status":"ok","service":"historical-data-service","version":"0.1.0-poc"}` - `/health/ready`: `{"status":"ready","db":"connected"}` - Trust-Gate-Code im Container importierbar - `BackfillOrchestrator.run` kennt `trust_gate` (additiv, Standard `None` = Gate inaktiv) --- ## 14. Bekannte Technical Debt (vorbestehend, NICHT Phase-13.4-Regression) Die folgenden 4 Fehler sind **vorbestehend** — mit dem unveränderten Pre-13.4-`backfill.py` identisch reproduzierbar (definitiv bewiesen): 1. **Aggregation E2E** — `TypeError: float - NoneType` (DB-Wahrheit, M5-Aggregation) 2. **Read API E2E** — `/bars M5` liefert 24 statt 12 Bars 3. **Parquet POC** — M5-Export/Reimport weicht ab 4. **Test-Fixture-Trennung** — operative Daten enthalten Feb-2024-Testbars Diese sind **nicht** durch das Trust Gate verursacht und werden **nicht** in Phase 13.4 behoben. --- ## 15. November-Daten - **Unverändert** (5760 Bars für 11-09 bis 11-12, alle `source_timestamp` NULL) - **Weiterhin UNTRUSTED** — als forensischer Beweis erhalten - Nicht gelöscht, nicht überschrieben, nicht reklassifiziert, nicht migriert, nicht repariert --- ## 16. Runner / Watchdog / OANDA / Production Gates - **Runner p13_eurusd:** PAUSED (unverändert) - **Watchdog:** unverändert, Recovery-only (Cron `fa8ab4da220f`, DUKASCOPY_RECOVERY ONLY) - **OANDA:** nicht integriert (keine Credentials, keine API-Calls) - **Production Gates:** unverändert - **DB-Schema:** unverändert (additiv, rückwärtskompatibel) --- ## 17. Nächster empfohlener Schritt (NOCH NICHT FREIGEGEBEN) Ein minimaler kontrollierter **End-to-End-Ingest-Test** soll später beweisen, dass das Trust Gate nicht nur in Unit-/Reproduction-Tests funktioniert, sondern im echten `DOWNLOAD → PROVENANCE → SANITY → TRUST → PERSIST`-Pfad UNTRUSTED/UNKNOWN-Daten tatsächlich VOR der Persistierung blockiert. **Dieser Test ist jetzt noch NICHT freigegeben.**