Files
ApiCore/docs/pruefung-2026-09.md
T
Nick SzaunigandClaude Opus 5.5 ec8c95056d
Backend Image / build-and-push (push) Successful in 6m43s
Frontend Image / build-and-push (push) Failing after 12m42s
Manifests / validate (push) Successful in 18s
docs: README im Root, Backend/Frontend-READMEs, Statuszeilen (2026-10-16)
Neues README im Root mit Zweck, Aufbau, Start, Prüfbefehlen, Betrieb und
Projektstand (Issues #44, #43). Backend- und Frontend-README ersetzen die
Framework-Vorlagen. CLAUDE.md: Ordnerstruktur und echte Arbeitsweise
(main, gebündelt pushen, Rollout nur mit Freigabe). ARCHITECTURE §7/§8:
Live-ConfigMap, ensure-secrets.sh, korrektes Admin-Seed-Verhalten.
.env.example um BACKUP_MAX_IMPORT_BYTES und SEED_ADMIN_* ergänzt.
Statuszeilen in Plänen Rückgrat, T3–T6 und Specs Fahrplan, T7.

Refs #42

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-16 08:12:49 +02:00

165 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Prüfung & Verbesserung ApiCore / Wabenhain – Arbeitsstand September 2026
ApiCore: entstanden auf dem Branch `improve/integrity-sync-audhd`, inzwischen vollständig in `main` (Commits vom 2026-09-27 bis 2026-10-01, ausgerollt mit dem Backend-Image `sha-f5b1964` am 2026-10-12).
Wabenhain: Commits direkt auf `main`, gepusht (u. a. `d38f430`, `5642f0c`, `3bc0693` liegen auf `origin/main`).
Offene Punkte aus diesem Bericht werden als Gitea-Issues geführt (ApiCore #39 „Offene Fachfragen aus der Prüfung September 2026“, #9 „AuDHD-Anpassung: Restpunkte“).
Verifikationsstufen: **CR** = durch Codeprüfung beurteilt, **AT** = automatisiert getestet,
**MP** = manuell im Browser geprüft (Desktop 1366 px + Mobil 390 px), **NV** = nicht verifiziert.
## Prüfmatrix
| Funktion | Erwartetes Verhalten | Befund (vorher) | Prio | Umsetzung | Status |
|---|---|---|---|---|---|
| Abfüllung bucht Honigmenge | Menge nie doppelt/zu viel abgebucht | Menge außerhalb der Transaktion gelesen, Absolutwert geschrieben → parallele Abfüllungen überbuchen, Abzug geht verloren | Kritisch | Bedingtes `UPDATE … WHERE amount >= x` in Transaktion; Korrektur/Löschen per Increment | AT |
| Lagerentnahme | Kein negativer Bestand | Ausgang ohne Bestandsprüfung | Hoch | Bedingtes Decrement, 400 bei Unterdeckung | AT |
| Völker umsetzen/teilen/vereinigen | Lebende Völker bearbeitbar | Nur Status `ACTIVE` erlaubt – schwache/weisellose Völker gesperrt (Vereinigung weiselloser Völker unmöglich) | Hoch | Nur Endzustände (aufgelöst, verkauft, tot, vereinigt) sperren; Selbst-Vereinigung + doppeltes Auflösen abgelehnt | AT |
| Königin umweiseln | Wechsel A → B möglich, Historie korrekt | Altes Volk behielt `currentQueenId` → Unique-Konflikt; Zuweisung an aufgelöste Völker möglich | Hoch | Altes Volk gibt Königin frei (→ weisellos), weiselloses Volk mit Königin → aktiv | AT |
| Aufgabe erledigen | Idempotent, Status/Zeit konsistent | Doppelklick verschob `completedAt`, Webhook doppelt; Wiedereröffnen behielt `completedAt` | Mittel | Zweites Erledigen = No-op; `completedAt` folgt Status | AT |
| Wiederkehrende Aufgaben | Fällige Aufgabe mit Termin | Erzeugte Aufgaben ohne Fälligkeit; verpasste Wochen einzeln nachgeholt | Mittel | Fälligkeit = Termin; verpasste Termine ergeben EINE Erinnerung | AT |
| Automatisierungen | Eine offene Aufgabe je Regel und Objekt | Jede Auslösung (z. B. jede Entnahme unter Mindestbestand) erzeugte neue Aufgabe + Warnung | Hoch | Kennzeichnung `sourceAutomationTrigger`, Duplikatsperre; optional `config.dueInDays` | AT |
| Varroa-Schwelle | Vergleich nur in gleicher Einheit | Probenzählwerte (Puderzucker/Auswaschen) und Brettzählung ohne Zeitraum gegen „Milben/Tag“ verglichen | Hoch | Nur natürlicher Milbenfall mit Tagesrate | AT |
| Offline-Warteschlange | Wiederholung erzeugt keine Duplikate | Verlorene Antwort → erneutes Senden → doppelte Durchsicht/Ernte + Automatik-Aufgabe | Hoch | `Idempotency-Key` (Opt-in je Endpunkt), Schlüssel im Queue-Eintrag | AT |
| Start mehrerer Replikas | Beide Pods starten | Berechtigungs-Sync auf leerer DB: Unique-Fehler, Pod-Absturz | Mittel | `createMany … skipDuplicates` | AT |
| Eingabeplausibilität | Serverseitige Grenzen | Skalen 1–5 nur im UI; doppelte Völker in Ernte → 500; Netto > Brutto möglich | Mittel | DTO-Grenzen, `ArrayUnique`, Gewichtsprüfung | AT |
| Wabenhain: Ernte-Beschreibung | Keine internen Daten öffentlich | Interne ApiCore-Ernte-Notizen in Shoptext kopiert | Hoch (Sicherheit) | Notizen nicht mehr übernommen | AT |
| Wabenhain: Entwürfe | Admin-Korrekturen bleiben | Jeder Sync überschrieb unveröffentlichte Entwürfe | Hoch | Im Admin bearbeitete Entwürfe (Audit-Log) geschützt, gemeldet | CR |
| Wabenhain: Abruf | Kein Hängen, Fehler ≠ leere Liste | Kein Timeout | Mittel | 20-s-Timeout; Tests: Fehler/Strukturfehler brechen ab | AT |
| Wabenhain: Sichtbarkeit | Letzter Lauf erkennbar | Nur direkt nach Klick | Niedrig | „Letzter vollständiger Lauf“ im Panel | CR |
| Start-/Heute-Ansicht | Nächster Schritt klar | Kennzahlen-Kacheln statt Aufgaben | Hoch (UX) | „Als Nächstes“ + „Außerdem heute“, Rückgängig | AT (Logik) + MP |
| Bewegung | Einstellbar, System respektiert | Nur `motion-safe`-Klassen | Mittel | Voll/Dezent/Aus je Gerät | MP |
| Etiketten-Schnittmarken | Anlegekante für Papierschneider | Keine Marken, Zuschnitt nach Augenmaß | Mittel | Haarlinien-Marken im Blattrand an jeder Schnittkante, je Vorlage abschaltbar | AT + MP |
| Folge-Kontrollen | Nachkontrolle nach Maßnahme | Keine | Mittel | Regeln „Nach Behandlung/Fütterung“, standardmäßig aus, Frist „Fällig nach (Tagen)“ | AT |
| Stockkarte je Volk | Vollständige Historie als PDF | Nur Jahresbericht | Mittel | `colony-record-card.pdf`, optional je Jahr | AT + MP |
| Bestandsbuch Tierarzneimittel | Pflichtangaben Art. 108 VO (EU) 2019/6 | Lieferant, Erwerbsbeleg, Tierarzt, Standort am Behandlungstag, Halter fehlten | Hoch (Recht) | Felder ergänzt, Stand je Behandlung gespeichert, PDF je Stand, fehlende Pflichtangaben als „FEHLT“ | AT + MP |
| Ein-/Auswinterung & Verluste | Winterverluste je Volk und Stand nachvollziehbar | Nur Volksstatus, kein Bezug zum Winter, keine Ursache | Mittel | `WinterCheck` je Volk/Winter/Saison, Verlustkategorien nach COLOSS, Übersicht mit Verlustrate; nicht ausgewinterte Völker zählen nie als überlebt | AT + MP |
| E2E-Tests | Grün | 23 rot (1 Produktfehler, 3 veraltete Tests) | – | behoben | AT |
## Prüfungen (letzter Lauf)
- Backend: Typecheck, ESLint, 550 Unit-Tests, 211 E2E-Tests (isolierte Postgres 17), `nest build` – grün.
- Frontend: Typecheck, ESLint, 48 Tests, `next build` – grün. Design-Detektor (impeccable): 0 Befunde.
- Wabenhain: Typecheck, ESLint (geänderte Dateien), 538 Tests – grün.
- Manuell (Browser, lokale Demo-Daten): Login, Heute-Ansicht Desktop/Mobil, Abhaken in Liste und
„Als Nächstes“, Rückgängig (stellt vorherigen Status her), Profil-Bewegung „Aus“ wirkt, Aufgabenliste mobil.
## Zuständigkeit (Quelle der Wahrheit)
ApiCore und Wabenhain bleiben getrennte Pakete; Verbindung nur über die HTTP-API von ApiCore.
| Information | Maßgeblich | Sync-Verhalten (Wabenhain) |
|---|---|---|
| Stand, Völker, Durchsichten, Behandlungen, interne Notizen | ApiCore | wird nicht übertragen |
| Ernte: Datum, Jahr | ApiCore | bei jedem Lauf aktualisiert |
| Ernte: Sorte, Ort, Text | Wabenhain | nur Vorbelegung unbearbeiteter Entwürfe |
| Charge: Losnummer, Abfülldatum, MHD, abgefüllte Gläser | ApiCore | bei jedem Lauf aktualisiert (Zuordnung über ApiCore-ID) |
| Charge: Shop-Bestand, Bilder, Ernte-Zuordnung | Wabenhain | nie geändert; Bestand > Abfüllmenge wird gemeldet |
| Produkt: Name, Text, Gewicht, Preis, Bilder, Sichtbarkeit | Wabenhain | nur beim ersten Anlegen vorbelegt |
| Bestellungen, Kunden, Bewertungen | Wabenhain | nie Richtung ApiCore |
Umgesetzt in Wabenhain `d38f430` (AT: Entscheidungslogik; CR: DB-Ablauf der Server-Action).
## Nächste Schritte
1. ~~Zuständigkeit pro Feld im Sync~~ – erledigt.
2. ~~Eigene, versionierte Sync-Schnittstelle~~ – erledigt: `GET /api/v1/shop-sync` (Vertragsversion 1, Feld-Allowlist, Berechtigung `shop-sync:read`). Bewusst ohne „geändert seit“: kleine Datenmenge, Voll-Abgleich heilt verpasste Läufe selbst. Wabenhain nutzt sie seit `5642f0c`; seit `3bc0693` ausschließlich (kein Rückfall auf andere Endpunkte).
3. ~~Nur-Lese-API-Schlüssel~~ – erledigt: Verwaltung → Automatisierung → API-Schlüssel. Nur Hash gespeichert, Klartext einmalig angezeigt, Scope nur `shop-sync:read`, widerrufbar, optional befristet, „zuletzt benutzt“ sichtbar. Wabenhain liest nur noch `APICORE_API_KEY`; der Passwort-Login ist entfernt (`3bc0693`).
4. Ende-zu-Ende-Test beider Systeme gegen Test-Instanzen (offen, Issue #39).
5. ~~Merge/Push~~ – erledigt.
## Offen / Entscheidungen
1. **Wabenhain-Sync gegen echtes ApiCore** nicht Ende zu Ende getestet (keine Zugangsdaten/Testinstanz
verbunden). Mapping, Client und Schutzlogik sind unit-getestet bzw. per Code geprüft.
2. ~~Service-Konto statt API-Schlüssel~~ – erledigt: nur noch API-Schlüssel mit Scope `shop-sync:read`.
3. **Status „blockiert“** existiert im Aufgabenmodell nicht. Nicht ergänzt (Schemaänderung + fachliche Definition nötig).
4. **Produktbeschreibung** aus ApiCore wird in Wabenhain übernommen und auf der öffentlichen
Herkunftsseite gezeigt – gilt damit als öffentlich. Falls sie intern gemeint ist: klären.
5. Automatische Vorschläge haben standardmäßig **keinen Termin** (keine erfundene Frist);
je Regel über `config.dueInDays` einstellbar.
6. ~~Übrige Formulare/Seiten nicht überarbeitet~~ – erledigt im Oktober 2026, siehe „AuDHD-Anpassung aller Seiten“.
## Bestandsbuch (Tierarzneimittel)
Grundlage: Art. 108 VO (EU) 2019/6, abgeglichen mit dem Muster-Formblatt des Veterinäramts Kassel
(Stand März 2026) und der FAQ des LGL Bayern. Gilt für jedes Tierarzneimittel, auch freiverkäufliche Säuren;
Aufbewahrung 5 Jahre.
- Export: Stand → „Bestandsbuch {Jahr}“ (`/export/treatment-log.pdf?year=&apiaryId=`), ein Blatt je Stand.
- Gleiche Behandlungen mehrerer Völker (Mittel, Charge, Menge, Zeitraum, Bezug) stehen in einer Zeile.
- Standort am Behandlungstag: seit dieser Version mit der Behandlung gespeichert (`Treatment.apiaryId`).
Für ältere Einträge aus der Umsetz-Historie abgeleitet; ist das unsicher, markiert `*` die Zeile.
- Fehlende Pflichtangaben erscheinen als „FEHLT“ statt leer – das Blatt sieht nie vollständiger aus, als es ist.
- Name und Anschrift des Halters: Verwaltung → Automatisierung → Stammdaten & Web. Erscheinen nur im
Bestandsbuch; die öffentliche Chargen-Seite gibt sie nachweislich nicht aus (Test in `public.service.spec.ts`).
## Überwinterung (Ein-/Auswinterung & Verluste)
- Seite „Überwinterung“ (Betrieb): je Winter (2026 = Winter 2026/27) eine Einwinterung und eine
Auswinterung pro Volk. „Als Nächstes“ nennt die noch nicht eingewinterten Völker; „Auswintern“ erscheint
erst nach der Einwinterung oder ab Januar.
- Verlustursachen: die drei Kategorien des COLOSS-Monitorings (tot/leer, unlösbares Königinnenproblem,
Naturereignis). Keine Diagnose-Liste – vermutete Gründe gehören in die Notiz.
- Verlustrate = verloren / eingewintert (wie COLOSS). Solange Völker nicht ausgewintert sind, heißt sie
„vorläufig“; ohne jede Auswinterung wird „–“ statt „0 %“ angezeigt.
- „Volk als abgestorben kennzeichnen“ setzt den Status nur auf ausdrücklichen Wunsch (Häkchen, vorbelegt),
in derselben Transaktion und mit Audit-Eintrag.
- Rechte `winter-checks:read/create/update`: per Migration an Imker und Mitarbeiter wie Durchsichten,
Nur-Lesen-Rollen nur lesen. Einträge sind im Backup enthalten.
- Quelle Verlustkategorien: COLOSS-Monitoring (z. B. Gray et al. 2020, J. Apic. Res., doi:10.1080/00218839.2020.1797272).
## API-Sicherheit (Oktober 2026)
Audit des gesamten Backends nach OWASP API Top 10 / ASVS L2 (25 Befunde), alle umgesetzt außer den zwei in ARCHITECTURE.md §9 begründeten Restpunkten. Verifikation: AT (Unit + E2E gegen Postgres 17).
| Befund | Schwere | Commit |
|---|---|---|
| MFA-Zwischen-Token galt als Access-Token (MFA-Bypass) | Kritisch | `e0bd7b2` |
| `users:update` konnte Admin-Konten übernehmen | Hoch | `c4a11b4` |
| SSO-Konto über unverifizierten E-Mail-Claim gewählt | Hoch | `a25265b` |
| Backup-Import überschreibt Benutzer; Export enthält Hashes/Secrets; Pfad-Traversal im Import | Hoch | `bd592ad` |
| Backup-Pfad/Zeitplan frei wählbar | Mittel | `ebeb499` |
| Webhooks: DNS-Rebinding, kein Timeout, Signatur ohne Zeitstempel | Mittel | `79d20ee` |
| TOTP-Replay, keine Kontosperre, MFA-Aus nur mit Passwort, Timing-Enumeration | Mittel | `5c666fe` |
| Refresh-Rotation nicht atomar, keine Höchstdauer | Mittel | `2de93bc` |
| Throttler nach Auth, Proxy-Vertrauen fest, Cache/CSP, Logs mit Query | Mittel | `9ae1d96` |
| API-Schlüssel ohne IP-Bindung, Scope über eigene Rechte hinaus | Mittel | `f1dc0de` |
| CSV-Formel-Injection | Mittel | `61e3247` |
| Passwortregel 8 Zeichen, kein Leak-Abgleich | Mittel | `0ba8996` |
| Suche umgeht Leserechte | Niedrig–Mittel | `eafe925` |
| Öffentliche Chargen-Seite, SSO-Callback-URL, Logout-Limit, 4xx-Mapping | Niedrig | `0899576` |
| SSO-/MFA-Token mehrfach nutzbar; Rechte-Cache je Replika | Niedrig–Mittel | `45efb08` |
Neue Umgebungsvariablen: `BACKUP_ENCRYPTION_KEY`, `BACKUP_ROOT`, `SESSION_MAX_AGE`, `TRUST_PROXY`, `PASSWORD_BREACH_CHECK` (siehe `.env.example`).
Betrieb nach dem Update: Alle Benutzer melden sich einmal neu an (Access-Tokens ohne Audience gelten nicht mehr). Webhook-Empfänger prüfen die neue Signatur (`X-ApiCore-Timestamp`.`X-ApiCore-Delivery`.Body). Im Wabenhain-Schlüssel die Server-IP als erlaubte Adresse eintragen.
## AuDHD-Anpassung aller Seiten (Oktober 2026)
Ziel: weniger gleichzeitig zu merken, keine Sackgassen, ein klarer nächster Schritt, handschuhtauglich.
Verifikation: AT (111 Frontend-Tests, je Formular eine Spec) + CR; MP (Browser) für die neuen Seiten noch offen.
Gemeinsame Bausteine (`src/components/form/*`, `query-state.tsx`, `detail-header.tsx`, `empty-state.tsx`):
| Regel | Umsetzung |
|---|---|
| Was ist Pflicht? | `Field` zeigt „Pflicht“/„optional“, Fehler direkt am Feld, Fokus aufs erste Problem |
| Nicht zu viel auf einmal | `FormSection`: nummerierte Schritte, seltene Felder eingeklappt mit Anzahl |
| Unterbrechung kostet nichts | `useFormDraft`: Entwurf je Benutzer auf dem Gerät, wird ausdrücklich angeboten, beim Abmelden gelöscht |
| Antippen statt Tippen | `RatingScale` (1–5 als Tasten mit benannten Enden), `ToggleChip` (Ja/Nein), Auswahl-Tasten statt Freitext |
| Keine Sackgasse | `QueryState`: Laden → Fehler mit „Erneut versuchen“ → leer mit nächstem Schritt → Daten |
| Ein Hauptschritt | `DetailHeader`: Überschrift, Zurück, Status als Symbol + Wort, eine Hauptaktion, Rest unter „Mehr“ |
| Handschuhe | Felder 44 px, Tasten ≥ 40 px auf dem Handy, Speichern bleibt in Daumenreichweite, Zifferntastatur bei Zahlen |
| Bewegung | Verschieben/Zoomen nur bei Bewegungsstufe „Voll“; Dialoge scrollen im Bildschirm |
| Riskantes | Rückfrage mit Klartext (Vereinigen, Zähler, Vorlagen, Zuordnungen), Rückgängig beim Etiketten-Designer |
Seiten: alle 14 Erfassungsformulare, Listen, Detailseiten (Volk, Stand, Königin, Honigcharge, Ernte,
Schleuderung, Abfüllung, Ausrüstung), Bearbeiten-Dialoge am Volk, Kalender (Tagesliste auf dem Handy),
Statistik, Überwinterung, Lager, Suche, Verwaltung (Benutzer, Audit-Log, Automatisierung, Backup).
Nebenbei behoben: Fütterungsstatistik addierte Liter und Kilogramm (jetzt je Einheit), Kalender ordnete
späte Einträge dem Folgetag zu (UTC), Zeichenfarbe der Königin wurde aus der ersten Ziffer geraten.
Offen: Prüfung im Browser (Desktop + Handy) der neuen Seiten; Bearbeiten-Dialoge außerhalb des Volks
(Honigcharge, Ernte, Abfüllung, Produkt) haben Zifferntastatur, aber noch nicht das volle Formular-Kit.