docs: Implementierungsplan Ausbaustufe 2
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
310
docs/superpowers/plans/2026-07-19-ausbaustufe-2.md
Normal file
310
docs/superpowers/plans/2026-07-19-ausbaustufe-2.md
Normal file
@@ -0,0 +1,310 @@
|
||||
# Ausbaustufe 2 — Implementierungsplan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Parser-Qualität auf Fable-Abnahmeniveau bringen, Konto-Namen und Betrag-Spalte verbessern, Version + Hilfe-Seite in die GUI, alle Anwenderdaten (inkl. Secrets) unter `~/.local/share/finance_pod`, ein gemeinsames Passwort für GUI und Grafana mit funktionierendem Dashboard-Embedding.
|
||||
|
||||
**Architecture:** Bestehendes System (FastAPI + Jinja2/HTMX, Postgres, Grafana, rootless Podman) wird erweitert, nicht umgebaut. Secrets wandern in das Bind-Verzeichnis des Pods, damit Backup+`create_pod_finance.sh` allein für Disaster-Recovery reichen. Grafana-Admin-Passwort wird bei jedem Skript-Lauf aus dem gemeinsamen `FB_PASSWORD` synchronisiert.
|
||||
|
||||
**Tech Stack:** unverändert (Python 3.12 im Container, venv 3.11 lokal, pytest, pdfplumber, Grafana OSS 12.1.0).
|
||||
|
||||
## Global Constraints
|
||||
|
||||
- Arbeitsverzeichnis `/home/wlfb/bin`, Tool-Code unter `finance/`; Phase „.env-Umzug" ändert zusätzlich Repo `/home/wlfb/fb` (dort separat committen).
|
||||
- Geldbeträge `Decimal`; Nutzertexte Deutsch; vor jedem Commit `cd /home/wlfb/bin/finance && .venv/bin/python -m pytest -q` grün (Basis: 61 passed).
|
||||
- Commit-Messages enden mit `Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>`.
|
||||
- DATENSCHUTZ: `tests/fixtures/*.pdf` sind echte Kontoauszüge. Keine echten Daten (Beträge, IBANs, Namen, Verwendungszwecke, Datumsangaben aus Auszügen) in Commits, Berichten, Code-Kommentaren oder Test-Dateien. Erwartungswerte für Tests liegen NUR in gitignorten `tests/fixtures/expected_*.json`.
|
||||
- Parser-Abnahme erfolgt durch den Controller (Fable) persönlich, nicht durch Subagenten-Selbsteinschätzung.
|
||||
- Deployment-Änderungen (Tasks 5–7) erst nach den App-Tasks; Redeploy gesammelt in Task 7.
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Parser-Audit-Werkzeug + DKB-Parser-Korrektur (Fable-Abnahme)
|
||||
|
||||
**Files:**
|
||||
- Create: `finance/scripts/parser_audit.py`
|
||||
- Modify: `finance/app/parsers/dkb.py` (nach Audit-Befund; `vr.py`/`hvb.py` nur falls Audit strukturgleiche Fehler zeigt)
|
||||
- Modify: `finance/tests/test_bank_parsers.py` (Noise-Checks + expected-Datei-Tests)
|
||||
- Modify: `finance/.gitignore` (Zeile `tests/fixtures/expected_*.json` ergänzen)
|
||||
|
||||
**Interfaces:**
|
||||
- Produces: `parser_audit.py` CLI: `python scripts/parser_audit.py tests/fixtures/dkb_beispiel.pdf` → gibt pro Transaktion eine nummerierte Zeile `#i | booking | value | amount | counterparty | purpose` aus, danach den Rohtext (via bestehender Extraktion). Nur lokale Nutzung; Ausgabe enthält echte Daten und darf nirgends persistiert werden außer im gitignorten `.superpowers/`-Workspace.
|
||||
- Produces: Testschema `tests/fixtures/expected_<bank>.json` (gitignored):
|
||||
```json
|
||||
{"transaction_count": 0,
|
||||
"spot_checks": [{"index": 0, "booking_date": "YYYY-MM-DD",
|
||||
"amount": "0.00", "counterparty_contains": "...",
|
||||
"purpose_contains": "..."}]}
|
||||
```
|
||||
|
||||
- [ ] **Step 1: Audit-Skript schreiben**
|
||||
|
||||
`finance/scripts/parser_audit.py`:
|
||||
```python
|
||||
"""Parser-Audit: geparste Transaktionen + Rohtext einer Fixture anzeigen.
|
||||
|
||||
NUR lokal verwenden - Ausgabe enthaelt echte Kontodaten und darf nicht
|
||||
in Commits, Reports oder Tickets uebernommen werden.
|
||||
Aufruf: python scripts/parser_audit.py tests/fixtures/dkb_beispiel.pdf
|
||||
"""
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pdfplumber
|
||||
|
||||
from app.parsers.registry import parse_pdf
|
||||
from app.parsers.validate import balance_difference
|
||||
|
||||
path = Path(sys.argv[1])
|
||||
stmt = parse_pdf(path)
|
||||
print(f"bank={stmt.bank} iban={stmt.iban} "
|
||||
f"period={stmt.period_start}..{stmt.period_end}")
|
||||
print(f"opening={stmt.opening_balance} closing={stmt.closing_balance} "
|
||||
f"diff={balance_difference(stmt)} n_tx={len(stmt.transactions)}")
|
||||
print("-" * 100)
|
||||
for i, t in enumerate(stmt.transactions):
|
||||
print(f"#{i:3d} | {t.booking_date} | {t.value_date} | {t.amount:>12} "
|
||||
f"| {t.counterparty[:40]:40} | {t.purpose[:60]}")
|
||||
print("=" * 100)
|
||||
with pdfplumber.open(path) as pdf:
|
||||
for n, page in enumerate(pdf.pages, 1):
|
||||
print(f"--- Seite {n} ---")
|
||||
print(page.extract_text() or "(kein Text)")
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Controller-Audit (Fable, kein Subagent)** — Der Controller führt das Skript für `dkb_beispiel.pdf` aus, vergleicht JEDE geparste Transaktion feldweise mit dem Rohtext (Buchungs-/Wertstellungsdatum, Betrag, Empfänger, Verwendungszweck, keine Seitenkopf-/Übertrag-/Saldo-Reste, keine verschluckten oder zusammengeklebten Buchungen) und schreibt eine Mängelliste nach `.superpowers/sdd/parser-audit-dkb.md` (gitignorter Workspace — Rohdaten-Zitate nur dort). Danach dasselbe kompakt für `vr_beispiel.pdf` und `hvb_beispiel.pdf` (Strukturprüfung; Detailkorrektur nur bei Befund).
|
||||
|
||||
- [ ] **Step 3: Parser fixen (Subagent, Mängelliste als Brief)** — Nur die im Audit benannten Defekte beheben; Interface `parse(path) -> ParsedStatement` und Modulstruktur unverändert. Nach jedem Fix: `pytest tests/test_bank_parsers.py -v` (Saldo-Gate bleibt Pflicht).
|
||||
|
||||
- [ ] **Step 4: Tests härten**
|
||||
|
||||
In `finance/tests/test_bank_parsers.py` ergänzen (committebar, ohne echte Daten):
|
||||
```python
|
||||
import json
|
||||
import re
|
||||
|
||||
NOISE = re.compile(
|
||||
r"(Kontostand|Übertrag|alter Saldo|neuer Saldo|Seite \d|Blatt \d)", re.I)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("filename,bank", CASES)
|
||||
def test_no_noise_in_parsed_fields(filename, bank):
|
||||
path = FIXTURES / filename
|
||||
if not path.exists():
|
||||
pytest.skip(f"Fixture {filename} fehlt")
|
||||
stmt = parse_pdf(path)
|
||||
for t in stmt.transactions:
|
||||
assert not NOISE.search(t.purpose), f"Noise im Verwendungszweck: #{stmt.transactions.index(t)}"
|
||||
assert not NOISE.search(t.counterparty), f"Noise im Empfänger: #{stmt.transactions.index(t)}"
|
||||
assert stmt.period_start <= t.booking_date <= stmt.period_end
|
||||
|
||||
|
||||
@pytest.mark.parametrize("filename,bank", CASES)
|
||||
def test_expected_values(filename, bank):
|
||||
path = FIXTURES / filename
|
||||
exp_path = FIXTURES / f"expected_{bank}.json"
|
||||
if not path.exists() or not exp_path.exists():
|
||||
pytest.skip("Fixture oder expected-Datei fehlt")
|
||||
stmt = parse_pdf(path)
|
||||
exp = json.loads(exp_path.read_text())
|
||||
assert len(stmt.transactions) == exp["transaction_count"]
|
||||
for c in exp["spot_checks"]:
|
||||
t = stmt.transactions[c["index"]]
|
||||
assert str(t.booking_date) == c["booking_date"]
|
||||
assert str(t.amount) == c["amount"]
|
||||
assert c["counterparty_contains"].lower() in t.counterparty.lower()
|
||||
assert c["purpose_contains"].lower() in t.purpose.lower()
|
||||
```
|
||||
|
||||
`expected_dkb.json` (und bei Befund `expected_vr.json`/`expected_hvb.json`) erstellt der CONTROLLER aus dem Audit (mind. 5 Spot-Checks über den Auszug verteilt, inkl. erster + letzter Buchung). Datei bleibt gitignored — `finance/.gitignore` um `tests/fixtures/expected_*.json` ergänzen.
|
||||
|
||||
- [ ] **Step 5: Fable-Abnahme** — Controller wiederholt Step 2 auf dem gefixten Stand; Abnahme erst, wenn feldweise keine Mängel mehr bestehen und die volle Suite grün ist.
|
||||
|
||||
- [ ] **Step 6: Commit** — `fix: DKB-Parser feldweise korrigiert, Audit-Werkzeug und gehaertete Parser-Tests` (Mängel im Commit-Body nur GENERISCH beschreiben, ohne echte Daten).
|
||||
|
||||
---
|
||||
|
||||
### Task 2: Konto-Namen + Betrag-Spalte
|
||||
|
||||
**Files:**
|
||||
- Modify: `finance/app/routers/accounts.py` (PATCH-Route), `finance/app/routers/gui.py` (Konten in Übersicht-Kontext), `finance/app/templates/index.html` (Umbenennen-Formular), `finance/app/templates/transactions.html` (Spaltenkopf „Betrag (€)", Zellen ohne €, rechtsbündig), `finance/app/static/style.css` (`.amount { text-align: right }`, `td.account { word-break: break-all; max-width: 12rem }`)
|
||||
- Test: `finance/tests/test_crud_api.py` (PATCH-Tests)
|
||||
|
||||
**Interfaces:**
|
||||
- Produces: `PATCH /api/accounts/{id}` mit Body `{"name": str}` (nur `name`, 1–100 Zeichen, führende/abschließende Leerzeichen gestrippt; leer → 422) → 200 mit AccountOut; 404 bei unbekanntem Konto.
|
||||
- GUI: In der Übersicht je Konto-Zeile Inline-Formular (Textfeld vorbefüllt mit aktuellem Namen, Button „Umbenennen", `hx-patch` via bestehender json-form-Extension auf `/api/accounts/{id}`, danach Reload/Refresh der Tabelle). Buchungsliste zeigt weiterhin `account_names`-Mapping — profitiert automatisch.
|
||||
|
||||
- [ ] **Step 1: Failing Tests** — in `finance/tests/test_crud_api.py`:
|
||||
```python
|
||||
def test_patch_account_name(client):
|
||||
acc = client.post("/api/accounts", headers=H, json={
|
||||
"bank": "DKB", "iban": "DE71", "name": "DE71", "type": "giro"}).json()
|
||||
r = client.patch(f"/api/accounts/{acc['id']}", headers=H,
|
||||
json={"name": "DKB Giro"})
|
||||
assert r.status_code == 200 and r.json()["name"] == "DKB Giro"
|
||||
assert client.patch("/api/accounts/9999", headers=H,
|
||||
json={"name": "x"}).status_code == 404
|
||||
assert client.patch(f"/api/accounts/{acc['id']}", headers=H,
|
||||
json={"name": " "}).status_code == 422
|
||||
```
|
||||
- [ ] **Step 2: Ausführen — FAIL** · **Step 3: Implementieren** (Route mit `AccountPatch(BaseModel)`; `field_validator` strippt und verwirft leere Namen) · **Step 4: Templates/CSS anpassen** · **Step 5: Suite grün** · **Step 6: Commit** — `feat: Konten umbenennbar, Betrag-Spalte mit Euro im Kopf`
|
||||
|
||||
---
|
||||
|
||||
### Task 3: Versionsanzeige
|
||||
|
||||
**Files:**
|
||||
- Create: `finance/VERSION` (Inhalt: `0.2.0`), `finance/app/version.py`
|
||||
- Modify: `finance/app/templates/base.html` (Footer), `finance/app/routers/gui.py` (Jinja-Global), `finance/app/main.py` (`GET /api/version`), `finance/Containerfile` (`COPY VERSION .`), `create_pod_finance.sh` (`API_IMAGE="localhost/finance-api:$(cat "$FINANCE_DIR/VERSION")"`)
|
||||
- Test: `finance/tests/test_gui.py`
|
||||
|
||||
**Interfaces:**
|
||||
- Produces: `app.version.get_version() -> str` (liest `VERSION` neben dem `app`-Paket, Fallback `"0.0.0-dev"`); `GET /api/version` (mit `require_auth`) → `{"version": "0.2.0"}`; Footer jeder GUI-Seite: `Finanzberatungs-Tool v0.2.0`.
|
||||
|
||||
- [ ] **Step 1: Failing Test**
|
||||
```python
|
||||
def test_version_visible(client):
|
||||
r = client.get("/api/version", headers={"Authorization": "Bearer test-key"})
|
||||
assert r.status_code == 200 and r.json()["version"] == "0.2.0"
|
||||
client.post("/login", data={"username": "admin", "password": "geheim"})
|
||||
assert "v0.2.0" in client.get("/").text
|
||||
```
|
||||
- [ ] **Step 2: FAIL** · **Step 3: Implementieren**
|
||||
|
||||
`finance/app/version.py`:
|
||||
```python
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def get_version() -> str:
|
||||
f = Path(__file__).resolve().parent.parent / "VERSION"
|
||||
try:
|
||||
return f.read_text().strip()
|
||||
except OSError:
|
||||
return "0.0.0-dev"
|
||||
```
|
||||
In `gui.py`: `templates.env.globals["app_version"] = get_version()`; in `base.html` vor `</body>`: `<footer class="version">Finanzberatungs-Tool v{{ app_version }}</footer>`.
|
||||
- [ ] **Step 4: PASS, Suite grün** · **Step 5: Commit** — `feat: Versionsanzeige (VERSION-Datei, Footer, /api/version, Image-Tag)`
|
||||
|
||||
---
|
||||
|
||||
### Task 4: Hilfe-Seite
|
||||
|
||||
**Files:**
|
||||
- Create: `finance/app/templates/hilfe.html`
|
||||
- Modify: `finance/app/routers/gui.py` (Route `GET /hilfe` mit `gui_session`), `finance/app/templates/base.html` (Nav-Link „Hilfe"), `finance/tests/test_gui.py` (Route in beide bestehende Seiten-Tests aufnehmen)
|
||||
|
||||
**Interfaces:**
|
||||
- Produces: `GET /hilfe` (Session-pflichtig, 302 → /login ohne Session).
|
||||
|
||||
- [ ] **Step 1: Tests erweitern** — `/hilfe` in `test_pages_require_login` und `test_pages_render_after_login` aufnehmen; zusätzlich `assert "Gebrauchsanleitung" in r.text`.
|
||||
- [ ] **Step 2: FAIL** · **Step 3: Implementieren** — `hilfe.html` erbt von `base.html`; Inhalt (vollständig, Deutsch — Feinschliff der Formulierungen erlaubt, Struktur bindend):
|
||||
|
||||
```
|
||||
# Gebrauchsanleitung
|
||||
|
||||
## Was dieses Tool ist
|
||||
Liquiditätsplanung auf Basis echter Kontoauszüge: Ist-Stand aus importierten
|
||||
Auszügen, Zukunft aus gepflegten Planposten und durchgerechneten Szenarien.
|
||||
|
||||
## Konzepte
|
||||
- Konten: entstehen automatisch beim ersten Import (per IBAN). Über die
|
||||
Übersicht umbenennbar (z. B. "DKB Giro").
|
||||
- Kontoauszug-Import: PDF in die Drop-Zone auf der Import-Seite ziehen (oder
|
||||
in die Inbox ~/.local/share/finance_pod/data/inbox/ legen und "Inbox
|
||||
scannen"). Jeder Import wird geprüft (Anfangssaldo + Buchungen = Endsaldo)
|
||||
und landet als Entwurf: erst nach Kontrolle der Vorschau "Übernehmen"
|
||||
klicken. Duplikate werden erkannt und nicht doppelt übernommen.
|
||||
- Kategorien & Regeln: Buchungen lassen sich kategorisieren; eine Regel
|
||||
("Muster im Text → Kategorie") kategorisiert künftige Importe automatisch.
|
||||
Regel direkt aus einer Buchung erzeugen: Button in der Buchungsliste.
|
||||
- Wiederkehrende Posten: Miete, Gehalt, Abos - mit Rhythmus (monatlich/
|
||||
quartalsweise/jährlich) und Fälligkeitstag. Der Vorschlags-Knopf erkennt
|
||||
Kandidaten aus mind. 3 Monaten gleichartiger Buchungen.
|
||||
- Einmalposten: einzelne künftige Zahlungen (z. B. Steuernachzahlung).
|
||||
- Kredite: Annuität oder endfällig; Tilgungsplan aufklappbar. Ein Kredit
|
||||
wirkt erst, wenn er einem Szenario zugeordnet ist.
|
||||
- Szenarien: eine "Was-wäre-wenn"-Rechnung. Basis = alle wiederkehrenden
|
||||
Posten + Einmalposten. Varianten entstehen durch Zuordnen von Krediten und
|
||||
Modifikatoren (Kategorie oder Posten prozentual/absolut kürzen oder ganz
|
||||
streichen). "Durchrechnen" liefert: tiefster Kontostand mit Datum, erstes
|
||||
Datum unter 0 und unter der Warnschwelle.
|
||||
- Grafana: Kontostand-Verläufe, Monatsausgaben nach Kategorie und der
|
||||
Szenario-Vergleich als Kurven (einmal mit demselben Passwort wie hier
|
||||
anmelden, dann erscheint das Dashboard auch in der Übersicht).
|
||||
|
||||
## Empfohlener Arbeitsablauf
|
||||
1. Monatlich: neue Auszüge importieren, Vorschau prüfen, übernehmen.
|
||||
2. Unkategorisierte Buchungen durchsehen; für Wiederkehrendes Regeln anlegen.
|
||||
3. Planung pflegen: Vorschläge prüfen, Einmalposten eintragen.
|
||||
4. Fragestellung ("Können wir uns X leisten?") als Szenario-Varianten
|
||||
abbilden und durchrechnen; Entscheidung anhand Tiefpunkt und
|
||||
Unterschreitungsdaten treffen, Kurven in Grafana vergleichen.
|
||||
|
||||
## Grundregeln
|
||||
- Bestätigte Buchungen sind die Wahrheit - nie ändern, nur kategorisieren.
|
||||
- Zukunft ausschließlich über Szenarien planen.
|
||||
- Empfehlungen immer mit durchgerechneten Zahlen begründen.
|
||||
```
|
||||
- [ ] **Step 4: PASS, Suite grün** · **Step 5: Commit** — `feat: Hilfe-Seite mit Gebrauchsanleitung`
|
||||
|
||||
---
|
||||
|
||||
### Task 5: Secrets-Umzug nach `$BIND_DIR/.env`
|
||||
|
||||
**Files:**
|
||||
- Modify: `create_pod_finance.sh`
|
||||
- Modify (Repo `/home/wlfb/fb`): `CLAUDE.md`, `.claude/skills/finanz-api/SKILL.md`, `.claude/skills/finanzberatung/SKILL.md`, `.claude/skills/auszug-import/SKILL.md` (überall Pfad `/home/wlfb/bin/finance/.env` → `$HOME/.local/share/finance_pod/.env`)
|
||||
|
||||
**Interfaces:**
|
||||
- Produces: `ENV_FILE="$BIND_DIR/.env"`; `$BIND_DIR` mit `chmod 700`; Migration: existiert `$HOME/bin/finance/.env` und `$ENV_FILE` nicht → `mv` + Meldung. Disaster-Recovery-Garantie: `$BIND_DIR` aus Backup + Skript-Lauf = lauffähig.
|
||||
|
||||
- [ ] **Step 1: Skript ändern** — Reihenfolge: `mkdir -p "$BIND_DIR" && chmod 700 "$BIND_DIR"` VOR dem `.env`-Block; dann:
|
||||
```bash
|
||||
ENV_FILE="$BIND_DIR/.env"
|
||||
LEGACY_ENV="$HOME/bin/finance/.env"
|
||||
if [ ! -f "$ENV_FILE" ] && [ -f "$LEGACY_ENV" ]; then
|
||||
mv "$LEGACY_ENV" "$ENV_FILE" && chmod 600 "$ENV_FILE"
|
||||
echo "MIGRIERT: .env nach $ENV_FILE verschoben"
|
||||
fi
|
||||
```
|
||||
- [ ] **Step 2: fb-Repo anpassen** — alle Pfad-Referenzen, inkl. Key-Extraktions-Muster: `grep '^FB_API_KEY=' $HOME/.local/share/finance_pod/.env | cut -d= -f2 | tr -d "'"` (Werte sind single-quoted!).
|
||||
- [ ] **Step 3: Verifizieren (ohne Redeploy, nur Trockenlauf)** — `bash -n create_pod_finance.sh`; Migration + Livegang erst in Task 7.
|
||||
- [ ] **Step 4: Commits** — `~/bin`: `feat: Secrets unter ~/.local/share/finance_pod (Backup-vollstaendig)`; `~/fb`: `docs: .env-Pfad auf finance_pod-Datenverzeichnis umgestellt`
|
||||
|
||||
---
|
||||
|
||||
### Task 6: Ein Passwort für GUI + Grafana, Embedding aktivieren
|
||||
|
||||
**Files:**
|
||||
- Modify: `create_pod_finance.sh`, `finance/app/templates/index.html` (Hinweis am iframe)
|
||||
|
||||
**Interfaces:**
|
||||
- Produces: `.env` führt `FB_PASSWORD='<klartext>'` (chmod 600; nötig, um Grafana synchron zu halten); `FB_GUI_PASSWORD_HASH` wird daraus abgeleitet; `GRAFANA_ADMIN_PASSWORD` entfällt. Grafana: `GF_SECURITY_ALLOW_EMBEDDING=true`, Admin-Passwort wird bei JEDEM Skript-Lauf per `grafana cli admin reset-admin-password` auf `$FB_PASSWORD` gesetzt (Invocation im Container verifizieren; `grafana-cli` als Fallback). Anonymer Zugriff bleibt AUS.
|
||||
- Passwort-Wechsel-Prozedur (in Skript-Kommentar dokumentieren): `FB_PASSWORD` in der `.env` ändern, Skript ausführen — Hash und Grafana ziehen nach.
|
||||
|
||||
- [ ] **Step 1: Bootstrap-Block umbauen** — Neuerzeugung: ein `FB_PASSWORD` generieren, Hash daraus ableiten, beide single-quoted schreiben; einmalige Terminal-Ausgabe des Passworts. Migration bestehender `.env` (hat `FB_GUI_PASSWORD_HASH`, aber kein `FB_PASSWORD`): neues `FB_PASSWORD` generieren, Hash NEU ableiten, `GRAFANA_ADMIN_PASSWORD`-Zeile entfernen, Passwort einmalig ausgeben (Rotation unvermeidbar, da Klartext aus Hash nicht rekonstruierbar).
|
||||
- [ ] **Step 2: Grafana-Container** — `-e GF_SECURITY_ALLOW_EMBEDDING=true`, `GF_SECURITY_ADMIN_PASSWORD="$FB_PASSWORD"`; nach Startwartezeit Sync-Kommando ausführen (idempotent, Fehler abfangen falls Grafana noch initialisiert — Retry-Schleife wie bei pg_isready).
|
||||
- [ ] **Step 3: iframe-Hinweis** — unter dem iframe in `index.html`: kleiner Text „Kein Diagramm sichtbar? Einmal in <a href=...>Grafana anmelden</a> (gleiches Passwort wie hier)."
|
||||
- [ ] **Step 4: `bash -n`, Suite grün (App unverändert bis auf Template)** · **Step 5: Commit** — `feat: gemeinsames Passwort fuer GUI und Grafana, Dashboard-Embedding`
|
||||
|
||||
---
|
||||
|
||||
### Task 7: Redeploy + Gesamt-Smoke + Disaster-Recovery-Probe
|
||||
|
||||
**Files:** keine neuen; führt Tasks 1–6 im Livesystem zusammen.
|
||||
|
||||
- [ ] **Step 1: Image + Redeploy** — `podman build -t "localhost/finance-api:$(cat /home/wlfb/bin/finance/VERSION)" /home/wlfb/bin/finance && bash /home/wlfb/bin/create_pod_finance.sh`. Migrationsmeldungen (Env-Umzug, Passwort-Rotation) protokollieren; neues Passwort NUR als letzte Zeile der Abschlussmeldung an den Nutzer.
|
||||
- [ ] **Step 2: Smoke** — Service aktiv; `/login` 200; GUI-Login mit `FB_PASSWORD` → 303+Cookie; `/`, `/buchungen`, `/planung`, `/import`, `/hilfe` 200; Footer zeigt `v0.2.0`; `/api/version` 200; Grafana-Login mit demselben Passwort (`curl -u admin:$FB_PASSWORD .../api/dashboards/uid/finanzen` → 200); Embedding-Header: `curl -sI http://127.0.0.1:8097/` enthält KEIN `X-Frame-Options: deny`; Datenbestand unverändert (1 Konto, 26 bestätigte Buchungen); anonymer Zugriff verweigert (`curl -s http://127.0.0.1:8097/api/dashboards/uid/finanzen` ohne Auth → 401/403).
|
||||
- [ ] **Step 3: Disaster-Recovery-Probe** — `systemctl --user stop pod-finance_pod.service`; alle finance-Units disablen und aus `~/.config/systemd/user/` entfernen; `podman pod rm -f finance_pod`; NUR mit vorhandenem `$BIND_DIR` das Skript erneut ausführen → alles wieder da (Login funktioniert, Datenbestand unverändert, keine Passwort-Neuerzeugung).
|
||||
- [ ] **Step 4: Ledger + Abschluss** — Ergebnisse in `.superpowers/sdd/progress.md`; offene Folgeentscheidung notieren: Neuimport des DKB-Auszugs (bestehende 26 Buchungen tragen ggf. alte Parser-Textfehler) — Nutzerentscheidung, nicht automatisch ausführen.
|
||||
|
||||
---
|
||||
|
||||
## Abschluss-Checkliste
|
||||
|
||||
- [ ] Suite grün (>= 65 Tests inkl. neuer Parser-/PATCH-/Version-Tests; expected-Tests laufen lokal, skippen ohne expected-Dateien)
|
||||
- [ ] Fable-Abnahme der Parser dokumentiert (`.superpowers/sdd/parser-audit-dkb.md`, ohne Commit)
|
||||
- [ ] Live: v0.2.0 im Footer, ein Passwort für GUI+Grafana, Embedding aktiv ohne anonymen Zugriff, `.env` unter `$BIND_DIR`, DR-Probe bestanden
|
||||
- [ ] Keine echten Kontodaten/Secrets in `git log -p` beider Repos
|
||||
- [ ] Beide Repos committet; Plan-Häkchen gesetzt
|
||||
Reference in New Issue
Block a user