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]>
15 KiB
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
Zuständigkeit pro Feld im Sync– erledigt.Eigene, versionierte Sync-Schnittstelle– erledigt:GET /api/v1/shop-sync(Vertragsversion 1, Feld-Allowlist, Berechtigungshop-sync:read). Bewusst ohne „geändert seit“: kleine Datenmenge, Voll-Abgleich heilt verpasste Läufe selbst. Wabenhain nutzt sie seit5642f0c; seit3bc0693ausschließlich (kein Rückfall auf andere Endpunkte).Nur-Lese-API-Schlüssel– erledigt: Verwaltung → Automatisierung → API-Schlüssel. Nur Hash gespeichert, Klartext einmalig angezeigt, Scope nurshop-sync:read, widerrufbar, optional befristet, „zuletzt benutzt“ sichtbar. Wabenhain liest nur nochAPICORE_API_KEY; der Passwort-Login ist entfernt (3bc0693).- Ende-zu-Ende-Test beider Systeme gegen Test-Instanzen (offen, Issue #39).
Merge/Push– erledigt.
Offen / Entscheidungen
- 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.
Service-Konto statt API-Schlüssel– erledigt: nur noch API-Schlüssel mit Scopeshop-sync:read.- Status „blockiert“ existiert im Aufgabenmodell nicht. Nicht ergänzt (Schemaänderung + fachliche Definition nötig).
- Produktbeschreibung aus ApiCore wird in Wabenhain übernommen und auf der öffentlichen Herkunftsseite gezeigt – gilt damit als öffentlich. Falls sie intern gemeint ist: klären.
- Automatische Vorschläge haben standardmäßig keinen Termin (keine erfundene Frist);
je Regel über
config.dueInDayseinstellbar. Ü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.