Phase 13.4: Historical Data Trust Gate (Provenance + Sanity Validation) - Doku - Rain Ocampo, 24.08.2026

This commit is contained in:
root 2026-08-24 11:05:32 +00:00
parent 4d421eab0d
commit 16a104822e

View file

@ -0,0 +1,206 @@
# 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,551,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.**