# 0015 — Prod-Deploy als sauberer git-Checkout + Citation-Block-Reklassifikation | | | |---|---| | **Status** | accepted | | **Datum** | 2026-05-10 | | **Supersedes** | (Anteile von) ADR 0004 (Tar-Upload-Workflow) | | **Refs** | v2.0.0-Tag, release/2.0-Branch, Commits 770d890–88211c5 | ## Kontext Beim 1.x → 2.0-Release-Cut wurden zwei strukturelle Probleme sichtbar, die in einer Entscheidung zusammenhängen. **Problem 1 — Prod-Repo-Mess.** Der prod-Pfad `/opt/gwoe-antragspruefer` wurde seit April per Tar-Upload aktualisiert (siehe ADR 0004). Effekt: Filesystem-Stand war neuer als der HEAD-Commit (4fbdc15 vom 10. April), 19 Files modifiziert, 30+ untracked. `git pull` war nicht mehr möglich, ohne den Stand zu zerstören. Dev wurde parallel sauber per `git pull` aus `main` deployed (Cron `auto-deploy.sh`). Die zwei Workflows divergierten. **Problem 2 — Citation-Block-Misattribution.** `reconstruct_zitate` (ADR 0001 / Issue #60) hat bei einem Cross-Kind-Fallback-Match nur die ``quelle`` des Zitats korrigiert, das Zitat aber im ursprünglichen Block belassen. Folge: Im wahlprogramm-Block standen Zitate aus Grundsatzprogrammen, die quelle stimmte zwar, aber die Block-Zuordnung suggerierte etwas Falsches. Reproduziert auf Antrag 18/18246 (NRW), Bewertung GRÜNE. ## Optionen ### Workflow 1 — Beide Probleme über separate Migrationen lösen **A.1 (Deploy):** Einmaliger Cut mit frischem `git clone`, dann beidseitig git-pull-basiert. Tar-Upload-Pfad obsolet. **A.2 (Deploy):** Tar-Upload retten, indem man HEAD nachzieht und .gitignore erweitert, sodass Tar-überschriebene Files nicht als Diff auftauchen. **B.1 (Citations):** Zwei-Pass-Verarbeitung in `reconstruct_zitate` — erst klassifizieren über beide Blöcke hinweg, dann schreiben. Plus einmaliges String-basiertes Migrations-Skript für die bestehenden 117 Records. **B.2 (Citations):** Ganze Bewertungen neu generieren (LLM-Call), sobald der Code-Fix lebt. ## Entscheidung **Deploy: A.1.** Einmaliger sauberer git-clone auf prod, danach beide Umgebungen identisch via `git pull`. Begründung: Der Tar-Mess wäre nie sauber zu reparieren gewesen, ohne irgendwo eine Annahme zu treffen, was "echt" ist. Ein frischer Clone setzt den Stand definitiv. Alle Volumes (`data/`, `reports/`, `backups/`) bleiben unangetastet. **Citations: B.1.** Code-Fix mit Zwei-Pass plus einmaliges Migrations-Skript. Begründung: B.2 wäre nicht-deterministisch (LLM-Fluktuation), würde Tokens verbrennen und liefert keine bessere Garantie als das deterministische String-Match auf "Grundsatzprogramm" vs. "Wahlprogramm" im quelle-Label. 22 Assessments wurden migriert, 26 Zitate verschoben. ## Konsequenzen ### Deploy - `/opt/gwoe-antragspruefer` ist seit dem Cut ein sauberer Checkout von `release/2.0`. - Nächster Standard-Deploy: `./scripts/deploy.sh` (Branch-Guard, Pre-flight, Pre-Deploy-Backup, Health-Check, Uptime-Kuma). - Major-Cuts: `./scripts/major-release-cut.sh ` — inkl. Bundle-Fallback bei Gitea-Korruption (war beim 2.0-Cut nötig). - Alter Pfad als `/opt/gwoe-antragspruefer-YYYYMMDD-HHMMSS-archive` archiviert. - ADR 0004 ist in Teilen abgelöst, der Tar-Upload-Pfad gilt nicht mehr. ### Citation-Binding - `reconstruct_zitate` klassifiziert pro Fraktion über beide Blöcke hinweg, schreibt erst danach in die jeweils passenden Blöcke. - Test: ``tests/test_embeddings.py::TestReconstructZitate::test_zitat_aus_grundsatzprogramm_landet_im_parteiprogramm_block`` reproduziert den 18/18246-Fall. - Migrations-Skript ``scripts/migrate-zitate-blocks.py`` ist idempotent und kann jederzeit re-run werden, falls weitere Records aus älterem Code-Stand reinkommen. ### DB-Wipe-Liste beim Major-Cut `scripts/major-release-cut.sh` enthält die Liste der Tabellen, die beim Cut geleert werden: - `assessments`, `assessment_versions` — Bewertungen (Schema-Drift möglich) - `presse_drafts`, `news_articles` — Cache-Daten - `auto_rate_runs`, `jobs` — Queue-/Cron-Tracking - `monitoring_scans`, `monitoring_daily_summary` — Live-Metriken - `auth_bypass_uses`, `comments`, `merkliste`, `bookmarks`, `email_subscriptions`, `votes` **Erhalten bleiben:** - `plenum_vote_results` — kostenlose Re-Ingest-Daten (PDFs werden vom Server geholt, kein LLM nötig) - `abgeordnetenwatch_votes`, `abgeordnetenwatch_polls` — re-fetchbar via `sync_abgeordnetenwatch.py`, aber zeitaufwändig - `embeddings.db` — extern als Volume, separat gehandhabt Beim 3.0-Cut diese Liste prüfen: falls neue User-Daten-Tabellen hinzukommen (z.B. erweiterte Bookmarks), gehört die Wipe-Entscheidung dort explizit gemacht.