Unveröffentlicht¶
Änderungen, die noch nicht in einem Release veröffentlicht wurden.
Hinzugefügt¶
Backend¶
- REQ-001 Stammdatenverwaltung: Botanische Familien, Arten, Sorten, Lebenszyklen (ArangoDB); Feld
propagation_methods(13 Vermehrungsarten, Mehrfachauswahl) im Arten-Steckbrief — alle 143 Kulturpflanzen-Seed-Daten befüllt - REQ-002 Standortverwaltung: Sites, Locations (rekursive Hierarchie), Slots, Standorttypen
- REQ-003 Phasensteuerung: Phasen-State-Machine (Keimung → Ernte), GDD/VPD/Photoperiod-Berechnung
- REQ-004 Dünge-Logik: Düngemittel, Nährstoffpläne, Dosierungen, Mischsicherheit, Spülung, Runoff, EC-Budget, Wasserquelle/CalMag-Korrektur
- REQ-006 Aufgabenplanung: Workflow-Templates, Tasks, Queue, Abhängigkeiten, HST-Validator
- REQ-007 Erntemanagement: Ernte-Indikatoren, Beobachtungen, Batches, Qualitätsbewertung, Karenz-Gate
- REQ-010 IPM-System: Schädlinge, Krankheiten, Behandlungen, Inspektionen, Resistenzmanager
- REQ-011 Externe Stammdatenanreicherung: GBIF + Perenual Adapter, Enrichment-Engine, Celery-Tasks
- REQ-012 Stammdaten-Import: CSV-Upload, Validierung, Vorschau, Bestätigung
- REQ-013 Pflanzdurchlauf: PlantingRun, Batch-Operationen, State-Machine
- REQ-014 Tankmanagement: Tanks, Tankzustände, Befüllungen, Wartung, Sensoren
- REQ-015 Kalenderansicht: iCal-Feeds, Aggregation, Token-basierter Zugriff
- REQ-019 Substratverwaltung: Erweiterte Substrattypen, Lebenszyklus-Manager, Wiederverwendung
- REQ-020 Onboarding-Wizard: 5-Schritte-Assistent, 9 Starter-Kits, Erfahrungsstufen
- REQ-022 Pflegeerinnerungen: 9 Pflegeprofile, FAMILY_CARE_MAP, adaptive Intervalle, Celery-Task
- REQ-023 Authentifizierung: Lokale Konten (bcrypt), JWT (authlib), Refresh-Token-Rotation
- REQ-024 Mandantenverwaltung: Tenant-Isolation, Mitgliedschaften, Einladungen, RBAC
- REQ-028 Mischkultur & Companion Planting: Graph-basierte Kompatibilität
- REQ-031 KI-Assistent: RAG-basierte Wissensdatenbank, LLM-Adapter (Anthropic/Ollama/OpenAI-kompatibel), Hybrid-Search
- Mandanten exportieren und importieren (Issue #2138, REQ-056 §8): Ein Garten lässt sich jetzt als Archiv sichern und als neuer Garten wieder einspielen — in dieselbe oder eine andere Installation. Exportieren darf die Leitung mit Verwaltung (
POST /api/v1/tenants/{slug}/exports, auch während der Löschfrist) oder ein Plattform-Admin (POST /api/v1/admin/platform/tenants/{key}/exports), jeweils mit Passwort-Bestätigung; höchstens ein Export läuft gleichzeitig (409 EXPORT_ALREADY_RUNNING) und höchstensTENANT_EXPORTS_PER_DAY(Standard3) je 24 Stunden. Alle Mitglieder mit Verwaltung bekommen eine E-Mail. Das Archiv enthält alle Fachdaten, die Verbindungen dazwischen, die Fotos und die Mitglieder ohne E-Mail-Adresse — keine Anmeldedaten, API-Schlüssel, Einladungen, Integrations-Zugangsdaten oder Protokolle — und ist 72 Stunden ladbar. Importieren darf nur ein Plattform-Admin (POST /api/v1/admin/platform/tenant-imports): Jeder Datensatz bekommt eine neue Kennung, Mitglieder werden über die E-Mail-Adressen bestehender Konten zugeordnet, und ihre Rolle legt der Admin fest, nicht das Archiv. Ein Archiv mit fremden Datensätzen, fehlenden Katalogeinträgen oder einer beschädigten Datei lehnt der Import mit422ab, ohne etwas zu schreiben. Der neue Garten bleibt bis zum letzten Datensatz unerreichbar (Wird importiert), ein Fehler mitten im Import entfernt alles wieder, und ein abgebrochener Import wird stündlich aufgeräumt. Archive tragen die Signatur der Installation; der Import meldet mitverified, ob ein Archiv unverändert von hier stammt. Schlüssel von Integrationen bleiben draußen, und jeder Download steht im Sicherheitsprotokoll. Neu sindTENANT_EXPORTS_PER_DAY,TENANT_IMPORT_MAX_UPLOAD_MBundRATE_LIMIT_TENANT_IMPORT. Wie groß ein Archiv entpackt werden darf, ist fest begrenzt (1 GiB, davon höchstens 128 MiB Datensätze, dazu Kompressionsverhältnis und Datensatzzahl), unabhängig vonTENANT_IMPORT_MAX_UPLOAD_MB. Der Eigentümer des neuen Gartens bekommt genau die Zusatzberechtigungen, die der Admin bestätigt, und deaktivierte Konten lassen sich nicht zuordnen. Auch eine einzelne riesige Zeile oder Millionen kleiner Werte lehnt der Import ab, bevor sie Speicher kosten; das Mitgliederlimit des Zielplans gilt auch für die importierten Mitglieder. Lässt sich ein gescheiterter Import nicht vollständig zurückrollen, bleibt der halbe Garten unerreichbar, der Import meldetcleanup_pending, und der stündliche Job löscht ihn. Auch abgelehnte und gescheiterte Importe stehen mit dem SHA-256 des Archivs im Sicherheitsprotokoll. Ein Archiv, das der Plan des neuen Gartens nicht fasst — mehr Mitglieder, mehr Speicher oder mehr Fotos je Pflanze —, lehnt der Import schon beim Antrag mit dem Code des Kontingents ab (MEMBER_LIMIT_REACHED,STORAGE_QUOTA_EXCEEDED,PHOTO_QUOTA_EXCEEDED), ohne etwas zu schreiben. Mit TimescaleDB nimmt das Archiv jetzt auch die Sensor-Messwerte des Gartens mit (timeseries/readings.csv: Rohwerte und Stunden-/Tageswerte, nur die des Gartens, gegen die Größengrenze des Exports gezählt und von der Signatur abgedeckt); das Archivformat bleibtkamerplanter-tenant-export/1, der Import übergeht die Datei und nennt im Bericht die Zahl der Zeilen. Neu istTENANT_EXPORT_SIGNING_SECRET: ein eigenes Geheimnis für die Archiv-Signatur (mindestens 32 Zeichen; leer = wie bisher ausJWT_SECRET_KEYabgeleitet; im Helm-Chart optional übertenantExport.signingSecret). Der Import hat jetzt seine eigene Upload-GrenzeTENANT_IMPORT_MAX_UPLOAD_MBstatt der allgemeinen von 26 MiB — nur diese Route, höchstens bisUPLOAD_SPOOL_BUDGET_MB(mit den Standardwerten etwa 51 MiB); das Helm-Chart setzt dafür auf genau diesem Pfad die Grenze des Frontend-nginx und eines eigenen Ingresstenant-import(tenantExport.importMaxUploadMb). Wie du/tmpfür größere Archive bemisst, steht im Runbook. Noch offen: das Einspielen der Messwerte. Die Oberfläche gibt es: Tab Export in den Garten-Einstellungen und die Seite Importe im Plattform-Modus (die Mitglieder-Schlüssel trägst du von Hand ein). Siehe Mandanten exportieren und importieren. - Pläne und Kontingente je Mandant (Issue #2142, REQ-056 §7): Jeder Mandant hat jetzt einen Plan (
plan_key), der seine Kontingente festlegt — Mitglieder, Service Accounts, Speicher, Fotos je Pflanze und die drei KI-Tagesbudgets. Alle Mandanten starten mit dem Planstandard, bei dem jedes Kontingent der bisherigen Server-Einstellung folgt; ohne weitere Pläne ändert sich also keine Grenze, auch keine per Umgebungsvariable angepasste. Plattform-Admins legen Pläne über/api/v1/admin/platform/plansan, ändern und löschen sie und ordnen sie mitPATCH /api/v1/admin/platform/tenants/{key}(plan_key) zu; jede Änderung steht im Sicherheitsprotokoll. Ein niedrigeres Kontingent entfernt nichts, es lehnt erst die nächste Anlage ab. Jedes Mitglied liest Plan und Verbrauch überGET /api/v1/tenants/{slug}/entitlements. Die Ablehnungscodes bleiben dieselben. Neu istTENANT_MAX_ORGANIZATIONS_PER_USER(Standard10,0= Organisationen legt nur der Betreiber an): Die bisher fest eingebaute Grenze von zehn Organisationen je Konto lässt sich einstellen, die Ablehnung hat jetzt den CodeORGANIZATION_LIMIT_REACHEDstattVALIDATION_ERROR(Status weiter422), und eine Organisation in Löschung (deleted) zählt nicht mehr mit. Achtung: In einem Plan heißt0bei Speicher und Fotos „unbegrenzt“ und bei den KI-Budgets „Budget aus“; die Plan-Antwort nennt solche Kontingente im Feldunlimited. Jedes Kontingent hat eine Obergrenze, ein beschädigter Plan fällt auf die Server-Einstellungen zurück, und der Mandantplatformfolgt immer den Server-Einstellungen. Beim Start legt Kamerplanter den Planstandardan, und die Migrationv0091trägt ihn bei jedem bestehenden Mandanten ein. - Pläne und Kontingente in der Oberfläche (REQ-056 §15, AK-45): Plattform-Admins pflegen Pläne auf der neuen Seite Pläne (Einstellungen > Plattform-Modus > Pläne verwalten): Liste mit Standard-Markierung und Zahl der Mandanten, Formular zum Anlegen und Ändern, Löschen nur für ungenutzte Pläne. Ein leeres Kontingent steht als „wie Instanz-Einstellung“, ein
0als „unbegrenzt“ bzw. „Budget aus“. Die Seite eines Mandanten zeigt Plan, Verbrauch je Kontingent und den Eigentümer und ordnet einen anderen Plan zu (nicht für den Mandantenplatform). Jedes Mitglied sieht im neuen Tab Kontingente der Garten-Einstellungen Plan und Verbrauch seines Gartens. Die AblehnungenORGANIZATION_LIMIT_REACHED,MEMBER_LIMIT_REACHED,SERVICE_ACCOUNT_LIMIT_REACHED,STORAGE_QUOTA_EXCEEDEDundPHOTO_QUOTA_EXCEEDEDerscheinen als verständlicher Text auf Deutsch und Englisch statt der englischen Server-Meldung. - Datenschutz: eigene Einwilligung für die KI-Wissensfrage (REQ-031 v2.6, REQ-025 v1.35). Neuer optionaler Einwilligungszweck
ai_knowledge_question(„KI-Wissensfrage an die Wissensbasis“): Er erlaubt, deine frei formulierte Frage im KI-Assistenten an die Wissensbasis und ihr Sprachmodell zu senden. Pflanzenwerte gehen nur mit, wenn du sie selbst zur Frage angibst — dann ist zusätzlichai_tenant_data_accessnötig. Jederzeit widerrufbar, wirkt ab der nächsten Frage. Er erscheint in der Einwilligungsliste unterGET /api/v1/privacy/consentsund wird überPOST /api/v1/privacy/consentserteilt. - Datenschutz: eigene Einwilligung für den Schädlings-Erkennungsindex (REQ-025 v1.36, REQ-044 v1.4, REQ-010 v1.7). Neuer optionaler Einwilligungszweck
pest_reference_contribution(„Foto-Beitrag zur Schädlingserkennung“): Ein selbst beigetragenes Schädlingsfoto, das eine Administratorin oder ein Administrator für alle sichtbar freigibt, wird damit als Zahlenabdruck (Embedding) samt Herkunft in den gemeinsamen Schädlings-Erkennungsindex übernommen; das Foto verlässt die Installation nicht. Jederzeit widerrufbar, Wirkung ab der nächsten Freigabe. Er erscheint unterGET /api/v1/privacy/consentsund im Tab Einwilligungen; erteilt wird er überPOST /api/v1/privacy/consents(einen eigenen Schalter in der Oberfläche gibt es noch nicht). - API:
CONSENT_REQUIREDnennt den fehlenden Zweck maschinenlesbar indetails[0].purpose(zusätzlich,fieldbleibtconsent) — eine Route kann aus zwei Zwecken ablehnen, und der Client muss nicht mehr die Meldung auslesen. - API: Cloud-Kennzeichnung der Wissensfrage. Die Antwort von
POST /api/v1/t/{slug}/ai/knowledge/askträgt zusätzlichprovider_typeunduses_cloud_provider, eingestuft wie beim Glossar nach dem System-Standard-Provider der Plattform (der Knowledge Service antwortet mit seinem eigenen Modell). - Einladungslink für mehrere Personen (Issue #2182, REQ-024 v1.34 AK-07): Beim Einladungslink legst du unter „Personen pro Link“ fest, wie viele Personen mit ihm beitreten können (1–100, voreingestellt 1 wie bisher). Jede Nutzung wird atomar gezählt, die Liste der Einladungen zeigt „Genutzt“ (z. B. „3 von 20“, auf dem Handy als Karte), und jede Nutzung zählt gegen das Mitgliederlimit. API:
POST /api/v1/tenants/{slug}/invitations/linknimmtmax_usesan, die Einladungsliste trägtmax_usesunduse_count. Scheitert ein Beitritt, wird die Nutzung zurückgegeben — eine inzwischen widerrufene Einladung bleibt dabei widerrufen. Eine Einladung in einen gesperrten, verwaisten oder zur Löschung vorgemerkten Garten lässt sich nicht annehmen (422); eine Nutzung wird nach einem gescheiterten Beitritt nur zurückgegeben, wenn die Mitgliedschaft nachweislich nicht entstanden ist. - OIDC-Auto-Join (Issue #2182, REQ-024 v1.34 AK-09, REQ-023 v1.49): Hat ein Anmeldeanbieter einen Standard-Mandanten (
default_tenant_key), tritt ein Konto, das beim ersten Anmelden über ihn neu entsteht, diesem Mandanten als Beobachter bei. Bisher war das Feld gespeichert, wirkte aber nicht. Nie in den Mandantenplatformoder einen persönlichen Garten; ein unbekannter, nicht aktiver, gelöschter oder voller Mandant wird still übersprungen, die Anmeldung gelingt. Der Beitritt steht im Sicherheitsprotokoll (via: oidc_auto_join). Nur bei einem generischen OIDC-Anbieter (bei Google, GitHub und Apple kann jede Person ein Konto anlegen) und nur für ein Konto, dessen E-Mail-Adresse der Anbieter als bestätigt meldet. Den Standard-Mandanten prüft Kamerplanter beim Speichern (Mandant vorhanden, kein persönlicher, nichtplatform; sonst 422), die Provider-Antwort zeigt ihn, undnulloder""entfernt ihn. Scheitert der Beitritt unerwartet, bleibt die Anmeldung davon unberührt. - Dokumentation (Issue #2179): Die Anwenderdoku nannte eine Plattform-Rolle
viewermit Lesezugriff auf den Admin-Bereich. Diese Rolle gibt es nicht; sie ist jetzt als „Noch nicht implementiert“ gekennzeichnet (REQ-024 §1a.4). - Upgrade vom Light- in den Full-Modus (Issue #1855, REQ-027 v1.9 §7a): Jeder Start merkt sich den Betriebsmodus (Collection
system_meta). Startet eine Light-Installation zum ersten Mal mitKAMERPLANTER_MODE=full, sperrt Kamerplanter das System-Konto des Light-Modus (is_active=false), macht seine API-Schlüssel ungültig, beendet seine Sitzungen und löscht seine Kalender-Abos. Ein im Light-Modus ohne Anmeldung erzeugterkp_-Schlüssel bekommt danach401(AK-35); Home Assistant und MCP-Clients brauchen einen neuen Schlüssel. Der Garten des Light-Modus bleibt unverändert. Neu sindGET /api/v1/system/takeover-statusundPOST /api/v1/system/takeover(nur im Full-Modus): Das erste registrierte Konto — oder ein schon vorhandener Plattform-Admin — übernimmt den Garten (accept: true, es wird Leitung mit beiden Verwaltungsrechten, die Daten bleiben, wo sie sind) oder lehnt ab; eine Instanz ohne Plattform-Admin macht es dabei zum ersten Plattform-Admin. Startet die Instanz wieder im Light-Modus, entsperrt Kamerplanter das System-Konto und gibt ihm seinen Garten zurück. Jede Änderung steht im Sicherheitsprotokoll (via: mode_switch/light_mode_takeover, neue Aktiontenant_taken_over). Nach dem Security-Review: Übernehmen und Ablehnen verlangen den Step-up des Kontos (neue Step-up-Aktionlight_mode_takeover); ohne Plattform-Admin entscheidet nur das erste nach dem Umstieg registrierte aktive Konto mit bestätigter Adresse und eigenem Garten; die Übernahme wird beansprucht und erst nach Erfolg entfernt, ein Fehler mittendrin wird zurückgenommen; der Full-Modus meldet das System-Konto unabhängig von der Datenbank nie an (auch nicht über MCP); neue Audit-Aktionentakeover_accepted,takeover_declined,takeover_reverted,system_account_sealed; ein Light-Start mit anderen Konten schreibt einecritical-Warnung. Nach der zweiten Review-Runde: Scheitert die Übernahme mittendrin (auch beim Protokollieren oder beim Plattform-Admin-Recht), nimmt Kamerplanter alles bereits Geschriebene zurück; gelingt auch das nicht, merkt es sich den Rest an der offenen Übernahme (needs_attention, Audittakeover_undo_failed) und nimmt ihn zurück, bevor jemand neu entscheidet. Abgewiesene Versuche stehen alstakeover_refusedim Sicherheitsprotokoll; einen Bestätigungscode bekommt nur, wer entscheiden darf. Nach der dritten Runde: Die gespeicherte Restquittung enthält das entscheidende Konto nicht mehr, das Zurücknehmen prüft vor jedem Schritt, ob die Entscheidung noch gilt, ein inzwischen gelöschter Garten blockiert die Übernahme nicht mehr dauerhaft, die Kontolöschung nimmt den Light-Garten nicht mit, solange eine Restquittung besteht, und beide Übernahme-Endpunkte sind auf 10 Aufrufe pro Minute und Konto begrenzt (RATE_LIMIT_TAKEOVER).
Frontend¶
- Übernahme-Dialog nach dem Upgrade auf Full (Issue #1855, REQ-027 Szenario ⅚): Nach dem Wechsel vom Light- in den Full-Modus fragt ein Dialog das erste Konto, ob es den bisherigen Garten übernehmen möchte, und zeigt, was darin liegt (Pflanzen, Standorte, Durchläufe, Nährstoffpläne, Aufgaben). „Ja, übernehmen“ wechselt direkt in den Garten, „Nein, neu starten“ lässt ihn für den Admin-Bereich stehen, „Später entscheiden“ fragt bei der nächsten Anmeldung wieder. Die Entscheidung bestätigst du mit deinem Passwort (oder der erneuten Anmeldung beim Anbieter bzw. einem Code).
- Alle REQ-001 bis REQ-024 Frontend-Seiten implementiert
- REQ-020 Onboarding-Wizard (5-Step MUI Stepper)
- REQ-021 UI-Erfahrungsstufen: Feldkonfiguration, Navigation-Tiering, ExperienceLevelSwitcher
- REQ-022 Pflege-Dashboard mit Urgency-Gruppierung
- Light/Dark Theme mit localStorage-Persistenz
- i18n Deutsch/Englisch (react-i18next)
Infrastruktur¶
- CI (Entwicklung): mypy-Ratchet für das Backend (Issue #2169).
strict = trueinsrc/backend/pyproject.tomllief bisher in keiner Prüfung — gemessen 3452 Befunde insrc/backend/app, darunter seit Juli der[call-arg]-Fehler, hinter dem #2150 stand.task typecheck:backend(Schritt „mypy ratchet“ im Backend-CI-Joblint-test, lokal zusätzlich als pre-commit-Hook) vergleicht die Befunde je Datei und Fehlercode mitsrc/backend/mypy-ratchet-baseline.json: Ein neuer Befund ist rot, eine neue Datei muss fehlerfrei sein. Fürcall-arg,name-defined,attr-definedundcall-overloadmuss die Zahl genau stimmen — behebst du einen solchen Befund, senkst du den Eintrag im selben Change mittask typecheck:backend -- --tighten. Der Lauf ist advisory, solangelint-testkein Pflicht-Check ist. -
Metriken (Prometheus): Das Backend liefert
http_request_duration_seconds,http_requests_total,http_requests_in_progressund Prozess-Metriken auf einem eigenen Port aus (METRICS_PORT, Standard0= aus; Issue #2129, MT-033, NFR-007 v1.1). Im Helm-Chart schaltetmonitoring.enabled: trueden Port 9464, einenServiceMonitor(Prometheus Operator) und die NetworkPolicybackend-metricsein, die nur die konfigurierten Prometheus-Pods auf diesen Port lässt./metricsist keine Route der API und über den Ingress nicht erreichbar. Labels:method,handler(Routenmuster, nie der Pfad),status— kein Mandant, keine Person. Neue Abhängigkeitprometheus-client(Apache-2.0, bündeltdecoratorunter BSD-2-Clause; Lizenz-Positivliste ergänzt). -
MkDocs-Dokumentationsinfrastruktur mit Material Theme und DE/EN i18n (NFR-005)
- ADR-001 bis ADR-006: Architektur-Entscheidungen dokumentiert
- Skaffold-basierter Entwicklungsworkflow mit Kubernetes/Helm
- pgvector + Embedding-Service für RAG-Pipeline
- Knowledge-Container für YAML-basierte Wissensbasis
- GitHub Actions CI/CD (Docker Lint/Build, Skaffold Verify)
Geändert¶
Dokumentation¶
- Light-Modus und KI: Die Spezifikation versprach, dass das Backend im Light-Modus nicht mit einem Cloud-Sprachmodell startet. Diese Prüfung gibt es nicht, und in der beschriebenen Form hätte sie nichts bewirkt — das Sprachmodell wählt der Knowledge Service über
LLM_PROVIDER. Die Seite Light-Modus sagt jetzt, dass du für den Light-Modus am Knowledge Service ein lokales Modell einstellen musst (Issue #2176). - Rollen und Berechtigungen, Plattform-Admin: Die Seiten sagen jetzt, was gilt — eine Standort-Zuweisung schränkt das Bearbeiten nicht ein, das erste registrierte Konto wird nicht automatisch Plattform-Administrator (eingerichtet wird es auf dem Server), ein Plattform-Administrator sieht keine Pflanzen oder Ernten fremder Gärten, und der geführte Umstieg vom Light-Modus ist als noch nicht gebaut gekennzeichnet.
- Mandanten & Gärten: Der Rollenvergleich nennt die aktuellen Rollen Leitung, Gärtner und Beobachter statt der abgeschafften Rolle „Admin“; Einladen, Rollen ändern und Einstellungen gehören zur Zusatzberechtigung Verwaltung. Eine Einladung vergibt die Rolle Beobachter (Issue #2181).
- Spezifikation, Auswahlwerte: 20 Wertelisten in REQ-001, -002, -004, -006, -007, -014 und -019 nannten andere Werte, als die Anwendung speichert — etwa Standorttyp ohne Fensterbank, Balkon und Growzelt, Erntequalität
A+statt des gespeichertena_plus(angezeigt wird weiterhin „A+“), Frostempfindlichkeittenderstattsensitive, Nährstoffphaseharveststattripening. Die Spezifikation nennt jetzt die Werte der Anwendung; an der Anwendung selbst ändert sich nichts. Eine automatische Prüfung vergleicht diese Wertelisten künftig mit dem Code (Issue #2183). - Spezifikation, Mandanten-Lebenszyklus und Kontingente (Issue #2143): Neue Anforderung REQ-056 führt Zustände, Sperre, Registrierung, Kontingente, Mandanten-Export, Eigentum und Ansprechperson eines Mandanten an einem Ort zusammen. Geplant und noch nicht umgesetzt sind Pläne mit eigenen Kontingenten (Standardplan
standardmit den heutigen Grenzen), ein Export des ganzen Mandanten mit Import in eine andere Installation, die Übergabe der Verantwortung für einen Garten und eine Ansprechperson für den Betreiber; Preise und Abrechnung sind ausdrücklich nicht Teil davon. Beim Messen fiel auf: Eine Sperre lässt sich umgehen, wenn ein Plattform-Admin einen gesperrten Mandanten löscht und dessen Verwaltung die Löschung abbricht (der Mandant ist danach wieder aktiv), und sechs automatische Abläufe — Tank-Warnungen, Home-Assistant-Messwerte, Wetterdaten, InvenTree-Abgleich (zwei) und der Benachrichtigungs-Digest — laufen für einen gesperrten Mandanten weiter. Die Admin-Anleitung behauptete, Kontingente und Limits eines Mandanten ließen sich einsehen — das ist als „Noch nicht implementiert“ gekennzeichnet. NFR-012 hat eine Kontingent-Tabelle (§10.3), auf die NFR-013 bisher ins Leere verwies.
Frontend¶
- Garten-Einstellungen: geplante Löschung abbrechen (Issue #2166). Ein Garten, dessen Löschung geplant ist, war für seine Mitglieder unsichtbar — er fehlte im Tenant-Selektor, und den Abbruch gab es nur für Plattform-Admins. Die Garten-Einstellungen — jetzt über den Tenant-Selektor mit Garten-Einstellungen erreichbar — zeigen einen Abschnitt Zur Löschung vorgemerkte Gärten mit Zustand und Löschdatum. Hast du in so einem Garten die Rolle Leitung und die Zusatzberechtigung Verwaltung, brichst du die Löschung dort mit Löschung abbrechen ab (Bestätigung mit deinem eigenen Passwort, einem E-Mail-Code oder einer erneuten Anmeldung); der Garten ist danach wieder aktiv und im Tenant-Selektor. Ein verwaister Garten wird angezeigt, lässt sich aber nicht zurückholen.
- Kalender, iCal-Feeds (Issue #2171): Die Abo-URL eines Feeds zeigt Kamerplanter nur noch einmal, direkt nach dem Anlegen oder nach dem Erneuern des Tokens, in einem eigenen Fenster mit URL kopieren und dem Hinweis, dass sie danach nicht mehr abrufbar ist. Die Feed-Liste zeigt keine URL und keinen Kopieren-Knopf mehr, sondern den Hinweis, dass du für eine neue URL den Token erneuerst. Token erneuern fragt jetzt vorher nach, weil die alte URL danach in keiner Kalender-App mehr funktioniert. Ein Klick neben das URL-Fenster schließt es nicht mehr; lässt der Browser das Kopieren nicht zu, sagt dir Kamerplanter, dass du die URL selbst aus dem Feld kopierst. Schlägt das Anlegen fehl, bleibt das Fenster mit deinem Namen offen, und Erstellen ist gesperrt, solange die Anfrage läuft — ein Doppelklick legt keinen zweiten Feed an.
- Datenschutz (Verhaltensänderung): Das Frontend meldet Fehler nur noch nach Einwilligung (Issue #2159, UI-NFR-013 CI-001/CW-003). Bisher startete der Browser das Fehler-Tracking-SDK, sobald eine
SENTRY_DSNgesetzt war, und das Einwilligungs-Banner war nirgends eingebunden. Jetzt erscheint bei gesetzter DSN beim ersten Besuch das Banner; das SDK wird erst nach Zustimmung zur Fehleranalyse geladen. Unter Datenschutz → Einwilligungen schaltet „Fehleranalyse erlauben" das Melden ohne Neuladen ab oder an, auch in anderen Tabs. Im Light-Modus erscheint kein Banner und das Frontend meldet keine Fehler. Ohne DSN ändert sich nichts. Für Betreiber: Frontend-Fehler kommen nur noch von Personen, die zugestimmt haben. - KI-Assistent (Verhaltensänderung): Die Seite KI-Assistent stellt Wissensfragen im Voll-Modus über deinen Garten (
/t/{slug}/ai/knowledge/ask) statt über den öffentlichen Pfad, den es dort nicht mehr gibt; im Light-Modus bleibt es bei/public/ai/ask(Issue #2175). Fehlt die Einwilligung „KI-Wissensfrage an die Wissensbasis“, bietet die Seite sie direkt an und sendet die Frage nach dem Einwilligen erneut. Ein ausgeschalteter KI-Schalter des Gartens und ein aufgebrauchtes Tageskontingent werden verständlich erklärt. Antwortet die Wissensbasis über ein Cloud-Modell, trägt die Antwort die Kennzeichnung Cloud-Verarbeitung wie die übrigen KI-Antworten. Welche Einwilligung fehlt, liest die Seite ausdetails[0].purpose; nur bei älteren Servern ohne dieses Feld aus dem Meldungstext. - Pflanzenerkennung (Verhaltensänderung): Der Schalter Als Referenzbild für die Erkennung nutzen ist jetzt die Einwilligung „Foto-Beitrag zur Pflanzenerkennung“ (Issue #2174). Eingeschaltet erklärt er, was geteilt wird; beim Speichern wird die Einwilligung erteilt, bevor das Foto beigetragen wird. Schlägt das fehl, wird die Pflanze nicht angelegt und ein Fehler angezeigt, statt den Beitrag still zu überspringen. Im Light-Modus ist der Schalter ausgeblendet.
- Datenschutz: Im Tab Einwilligungen lässt sich jede erteilte optionale Einwilligung mit Widerrufen zurücknehmen; bisher ging das nur über die API. Der Link „Datenschutz-Einstellungen“ im Einwilligungsdialog der Pflanzenerkennung führt jetzt auf die Datenschutz-Seite statt auf die Kontoeinstellungen.
- Glossar: Die Schaltfläche Ausführliche Erklärung erzeugen verschwindet, sobald die KI-Funktionen für den Garten oder die Installation ausgeschaltet sind; an ihrer Stelle steht der Grund statt eines Erneut-Versuchen-Knopfs, der genauso abgelehnt würde.
- Einstellungen: Den Reiter „Plattform-Modus" sehen nur noch Plattform-Admins; für alle anderen fragt die Seite keine Admin-Daten mehr ab (Issue #2144).
- Admin, Einladungen, KI-Assistent, Aufgaben einer Pflanze und Lagerbeobachtungen laden ihre Listen seitenweise vollständig, seit die Schnittstelle dort nur noch 50 Einträge pro Seite liefert (Issue #2131) — du siehst weiterhin alle Einträge.
- Mitglieder und Standort-Zuweisungen eines Gartens, die Mitgliederliste in der Mandantenverwaltung, die Durchläufe einer Workflow-Vorlage und die InvenTree-Verknüpfungen laden ebenfalls seitenweise vollständig (Issue #2131) — du siehst weiterhin alle Einträge.
- Datenschutz: Der Dialog zum Löschen des Kontos nennt vor der Bestätigung die Organisationen, in denen die Verwaltung an die dienstälteste Leitung übergeht oder die verwaisen (Issue #2134)
- Admin: Die Mandantenverwaltung zeigt den Zustand eines Mandanten (aktiv, gesperrt, Löschung geplant, verwaist, wird gelöscht) samt Löschdatum und bietet bei geplanter Löschung Löschung abbrechen mit Passwortbestätigung; Aktiv-Schalter und Löschen sind währenddessen gesperrt (Issue #2123)
- Abmelden — und das Abmelden, weil die Sitzung anderswo beendet wurde — leert jetzt alles, was der Browser für das Konto hielt: jede geladene Liste, den aktiven Garten (
kp_active_tenant_slug) und die Browser-Push-Registrierung dieses Geräts (Issue #2117, MT-020). Auf einem gemeinsam genutzten Gerät sieht die nächste Person nichts mehr von den Daten des vorherigen Kontos und erhält keine seiner Push-Nachrichten. Ein Abmelden, dessen Anfrage scheitert (offline), meldet den Tab trotzdem ab. Reste aus dem Light-Modus im Browser (lokale Favoriten, Modul-Sichtbarkeit, Dashboard-Layout) übernimmt nur das erste Konto, das sie auf diesem Browser übernimmt. - Antwortet eine Anfrage erst, nachdem du dich abgemeldet hast (oder abgemeldet wurdest), landet ihr Ergebnis nicht mehr im Tab (Issue #2253): Eine langsame Liste legt die Einträge des vorherigen Kontos nicht zurück, ein spät geladenes Gartenverzeichnis stellt dessen aktiven Garten nicht wieder her, und ein spät geladenes Profil meldet den Tab nicht wieder als dieses Konto an. Anfragen, die nach dem Abmelden starten, sind davon nicht betroffen.
- Admin: Auf der Seite eines Mandanten gibt es den Abschnitt Home-Assistant-Entitäten — Inventur der Instanz mit Suche, Schalter je Entität und Freigabe per ID für Benachrichtigungsdienste (Issue #2112). Sensor- und Wetterquellen-Dialog fragen die Home-Assistant-Liste nur noch mit dem Recht Technik ab und erklären sonst, warum sie fehlt; ohne Freigaben weist der Sensor-Dialog auf den Plattform-Admin hin.
- Anmeldung und Registrierung folgen dem Registrierungsmodus (Issue #2132): Ist die Registrierung geschlossen, fehlt der Registrieren-Link und die Registrierungsseite erklärt es; ist sie nur mit Einladung möglich, fragt die Seite nach dem Einladungscode (
/register?invitation=<code>füllt ihn vor). Eine abgelehnte Anmeldung über einen OIDC-Anbieter erklärt den Grund. - Einladungen: Ist ein Garten voll, erklärt die Seite zum Annehmen einer Einladung das in deiner Sprache — und dass die Einladung gültig bleibt, bis ein Platz frei wird (Issue #2133)
- Benachrichtigungen: Gespeicherte Apprise-URLs erscheinen maskiert (
tgram://****#1), ein Hinweis unter dem Feld erklärt, wie du sie behältst, entfernst oder ergänzt; eine neue Zeile lässt sich jetzt auch per Enter beginnen (Issue #2113). - Admin: Nach dem Löschen einer Organisation meldet die Oberfläche „Löschung angenommen“ statt „Organisation gelöscht“ — die Löschung läuft im Hintergrund (Issue #1792)
- Pflanzinstanzen werden überall mit einem sprechenden Namen angezeigt (z. B.
BASIL-001 (Basilikum – Genovese)) statt nur der technischen Instanz-ID; die Instanz-ID bleibt als sekundäre Information erhalten - Aufgaben: Die Formulare in den Tabs Bearbeiten und Abschließen zeigen Validierungsfehler jetzt als Hinweistext direkt am betroffenen Feld statt als kurzlebige Browser-Sprechblase (
noValidate) - Konto: Die Seite zur E-Mail-Bestätigung bietet auch im Fehlerfall (ungültiger oder abgelaufener Link) einen Anmelden-Button — vorher war diese Seite eine Sackgasse
Backend¶
- Schnellere Aufgaben-Warteschlange bei vielen Gärten (Issue #1534): Warteschlange, überfällige Aufgaben und die nach Status gefilterte Aufgabenliste eines Gartens lesen jetzt nur noch die Aufgaben dieses Gartens. Bisher las jede davon alle offenen Aufgaben der ganzen Installation — gemessen bei rund einer Million Aufgaben in 3 400 Gärten etwa 1,4 s je Abfrage für einen kleinen Garten, jetzt rund 2 ms. Den neuen Datenbank-Index legt Kamerplanter beim nächsten Start an (Migration
v0092); für dich ändert sich sonst nichts. - Behoben (Sicherheit): Upload-Grenze greift vor dem Einlesen, Anhänge kommen nicht mehr ungefragt aus dem Browser-Cache (Issue #2254, NFR-013 v1.13). FastAPI liest einen Upload vollständig ein — Dateien über 1 MiB nach
/tmp—, bevor Anmeldung oder Größenprüfung laufen. Gemessen: Ein 50-MiB-Upload gegen eine Grenze von 1 MiB wurde bis zum letzten Byte gelesen, auch ohne Anmeldung. Jetzt prüft das Backend die Größe vorher: Jeder Upload ist aufSTORAGE_MAX_FILE_SIZE_MB+ 1 MiB begrenzt (dieselbe Grenze wie der Frontend-nginx). Mit größererContent-Lengthantwortet es413(REQUEST_BODY_TOO_LARGE), ohne ein Byte zu lesen; ein Body ohne Längenangabe wird an der Grenze abgebrochen. Gleichzeitige Uploads teilen sich ein Budget für/tmp(UPLOAD_SPOOL_BUDGET_MB, Standard 52, gezählt in angekommenen Bytes); wer nachUPLOAD_SPOOL_WAIT_SECONDS(30) keinen Platz bekommt, erhält503mitRetry-After. Downloads per Token-Link (/api/v1/attachments/token/…) tragenCache-Control: no-storestattprivate, max-age=86400: Der Link gilt 15 Minuten, die Kopie im Browser hätte einen Tag gehalten, auch nach dem Löschen des Anhangs. Anhänge und Vorschaubilder in der App tragenprivate, no-cachemitETag: Der Browser fragt vor jeder Anzeige nach und bekommt304, wenn sich nichts geändert hat und du den Anhang noch sehen darfst. Abmelden, entzogene Mitgliedschaft und Löschen wirken so sofort. Der CSV-Import, die Schädlingserkennung und die Pflanzenbestimmung lesen höchstens ein Byte über ihre Grenze ein. Alle anderen Requests (JSON, Formulare) dürfen höchstens 1 MiB groß sein. Freigegebene Schädlingsbilder tragen ebenfallsprivate, no-cachestattpublic, max-age=86400, und dasETagaller Bild-Downloads verrät nicht mehr den Hash der hochgeladenen Originaldatei. Ein zu großes Foto in der Schädlingserkennung antwortet413statt422, sodass die App wieder ihre Meldung „Bild zu groß“ zeigt. EineContent-Lengthwie²führte vorher zu einem500. - Behoben (Sicherheit): Home Assistant, InvenTree und Web-Push lassen sich nicht mehr auf interne Adressen umlenken (Issue #2249). Die SSRF-Prüfung löste den Hostnamen auf und prüfte die Adressen; beim Verbindungsaufbau löste der HTTP-Client den Namen aber erneut auf. Ein Name, der der Prüfung eine öffentliche und der Verbindung eine interne Adresse liefert (DNS-Rebinding), erreichte so
127.0.0.1oder Cluster-Dienste — bei InvenTree setzt die technische Verwaltung jedes Mandanten die URL selbst, auch mithttp. Jetzt prüft die Verbindung selbst die Adressen, zu denen sie aufgebaut wird, nach derselben Regel, und verbindet sich nur mit einer geprüften Adresse;Host-Header, TLS-SNI und Zertifikatsprüfung bleiben beim Hostnamen. Das gilt für jeden Aufruf von Home Assistant (auch „Verbindung testen“) und InvenTree. Web-Push folgte einer Weiterleitung (301–308) des Push-Dienstes zu jedem beliebigen Ziel, ungeprüft; jetzt wird keiner Weiterleitung mehr gefolgt, die Zustellung zählt als fehlgeschlagen. Ein Web-Push-Aufruf hat außerdem ein Zeitlimit von 10 s (bisher keins). Eine fehlerhafte URL wiehttp://[::1lehnen die SSRF-Prüfungen jetzt mit422ab statt mit einem Serverfehler. An legitimen Verbindungen ändert sich nichts; die UmgebungsvariablenHTTP(S)_PROXYgelten für Home Assistant und InvenTree nicht mehr, weil ein Proxy den Namen selbst auflösen würde. Achtung: Für Home Assistant und InvenTree gelten jetzt auch CGNAT-Adressen (100.64.0.0/10, etwa Tailscale) und IPv6-Site-Local (fec0::/10) als privat und brauchenHA_ALLOW_PRIVATE_ENDPOINTbzw.INVENTREE_ALLOW_PRIVATE_ENDPOINT; eine in IPv6 eingebettete IPv4-Adresse (6to4, NAT64, IPv4-mapped) wird wie diese IPv4-Adresse beurteilt, und die Metadaten-Adressen100.100.100.200undfd00:ec2::/32sind immer gesperrt. Web-Push: verbleibendes Risiko. Für Web-Push schließt #2249 nur die Weiterleitungen. Der Push-Endpunkt wird vor dem Senden geprüft, beim Verbinden aber erneut aufgelöst; ein umgebogener Name endet am TLS-Zertifikat, Kamerplanter öffnet dabei aber eine TCP-Verbindung zur internen Adresse. Der Fehlertext einer fehlgeschlagenen Zustellung nennt deshalb nur nochdelivery failedstatt des Ausnahmetyps. - Behoben (Sicherheit): Ein Löschabbruch hebt eine Sperre nicht mehr auf (REQ-056 §4.3, REQ-024 v1.36 AK-65). Plante ein Platform-Admin die Löschung eines gesperrten Mandanten, konnte dessen eigene Verwaltung über
POST /api/v1/tenants/{slug}/erasure/cancelabbrechen — und der Mandant war danachactive, die Sperre also umgangen. Jetzt merkt sich das Planen der Löschung den bisherigen Zustand (status_before_deletion), und der Abbruch stellt ihn her: Ein gesperrter Mandant bleibt gesperrt, die Verwaltung darf die Löschung aber weiterhin abbrechen und so den Datenverlust verhindern. Eine vor dieser Änderung geplante Löschung weiß nicht, ob sie eine Sperre eingefroren hat: Sie kann nur ein Platform-Admin abbrechen (danachactive); die Verwaltung erhält403(keine Migration). Alle Zustandswechsel (Sperren, Reaktivieren, Löschung planen, abbrechen) sind jetzt bedingte Schreibvorgänge: Hat sich der Zustand zwischen Lesen und Schreiben geändert, antwortet die Anfrage mit409und ändert nichts. Eine wiederholte Löschanfrage überschreibt den gemerkten Zustand nicht mehr und macht eine verwaiste Organisation nicht mehr abbrechbar. Die Löschung eines gesperrten Mandanten kann nur ein Platform-Admin planen (403für jeden anderen Weg, gleiche Meldung). Nebenbefund: Nach einem Abbruch blieb das Löschdatum (deletion_scheduled_at) am wieder geöffneten Mandanten gespeichert, weil das Mandanten-Repository ein aufnullgesetztes Feld nicht schrieb; ein explizitesnullentfernt jetzt das Feld — nur beideletion_scheduled_at,status_before_deletionunddescription, jedes andere Feld lehnt das Repository ab. - Behoben (Sicherheit): Hintergrundjobs lassen gesperrte und zur Löschung vorgemerkte Mandanten aus (REQ-056 §5.2). 13 periodische Aufgaben fragten den Mandantenzustand nicht und arbeiteten für einen gesperrten oder zur Löschung vorgemerkten Mandanten weiter: Tank-Warnprüfung, E-Mail-Tagesbericht (stellte auch Benachrichtigungen über den Mandanten aus der Zeit vor der Sperre zu), Home-Assistant-Messwerte, Wetter, Klimanormalwerte, Winterhärtezonen, Bewässerungsbedarf, beide Saison-Läufe, beide InvenTree-Synchronisationen, Aktor-Regelkreis und Aktor-Statusabgleich. Alle fragen jetzt
ActiveTenants. Jeder Eintrag imbeat_scheduleist inTENANT_WRITING_BEATSoderTENANT_AGNOSTIC_BEATS(app/tasks/tenant_gate.py, mit Begründung) eingeordnet; der Guardtests/unit/guards/test_beats_skip_inactive_tenants.pyschlägt bei einem neuen, nicht eingeordneten oder nicht gesperrten Beat fehl. Aufbewahrung, Löschung, Speicher-Abgleich und globale Katalogjobs laufen weiter. Aktoren eines geschlossenen Mandanten bleiben nicht hängen: Für einen gesperrten, zur Löschung vorgemerkten oder verwaisten Mandanten laufen keine Zeitpläne, Übersteuerungen und Sensorregeln mehr; der Regelkreis fährt seine Geräte stattdessen in einen sicheren Zustand. Sicherheitsregeln greifen weiter; sonst wird jedes Gerät, das ein Notfall-Szenario ausschaltet (Ventile, Pumpen, Heizung, Licht, CO₂ …), einmal ausgeschaltet — der Abluftventilator läuft weiter, undfail_safe_stateam Gerät geht vor. Manuelle Aktoren bleiben unberührt, und in den geschlossenen Mandanten werden keine Ersatzaufgaben geschrieben. - Behoben (API): Die Mitgliederliste meldet die Zusatzberechtigungen (Issue #2166).
GET /api/v1/tenants/{slug}/memberslieferte für jedes Mitgliedadmin_scopes: [], auch für Mitglieder mit Verwaltung oder Technik — die Datenbankabfrage las das Feld nicht. Jetzt stehen die gespeicherten Zusatzberechtigungen in der Antwort. Neu (API):GET /api/v1/tenants?include_scheduled_deletion=truelistet zusätzlich die Mandanten inpending_deletionundorphaned; jeder Eintrag trägt jetztstatusunddeletion_scheduled_at. Ohne den Parameter bleibt die Liste wie bisher (nur aktive Mandanten). - Behoben: Der Test einer noch nicht gespeicherten Wetterquelle (
POST /api/v1/t/{slug}/sites/{key}/weather-sources/test) antwortete mit500, wennkindnicht zur Quelle passte (etwadwdalshome_assistant). Jetzt kommtreachable: falsemit einer Meldung, die den Widerspruch nennt (Issue #2169). - Behoben (Datenschutz, Issue #2166): Beantragt die letzte Person mit Verwaltungsrecht einer Organisation die Löschung ihres Kontos, wird die Organisation jetzt beim Antrag geregelt — die dienstälteste Leitung übernimmt, oder die Organisation verwaist und wird nach der Frist gelöscht — und die Mitglieder bekommen sofort eine E-Mail. Bisher geschah das erst beim endgültigen Löschen bis zu 90 Tage später; so lange konnte niemand die Organisation verwalten, weil das Konto ab dem Antrag geschlossen ist. Die Frist einer verwaisten Organisation läuft ab dem Antrag. Ein Kontolöschantrag lässt sich nicht zurücknehmen, deshalb wird auch die Übergabe nicht zurückgenommen (REQ-025 v1.37 §3.1.3 Regel 4).
- Behoben (Mandanten, Issue #2166): Die täglichen Erinnerungs- und Aufgaben-Beats (Pflege-Erinnerungen, Pflege-Benachrichtigung, Tageszusammenfassung, Eskalation, Tank-Wartung, Runoff-Spülung, Frostwarnung, Gieß- und Düngeaufgaben, Home-Assistant-Abgleich der Tanks) lassen einen Mandanten aus, der gesperrt, zur Löschung vorgemerkt oder verwaist ist. Bisher erzeugten sie dort weiter Aufgaben, lasen seine Home-Assistant-Sensoren und benachrichtigten die Mitglieder über einen Garten, den sie nicht öffnen können (REQ-024 v1.32 AK-65).
- Behoben (Mandanten, Issue #2166): Als verbleibende Verwaltung einer Organisation zählt nur noch ein Konto, das handeln kann. Hat eine zweite Person mit Verwaltungsrecht die Löschung ihres Kontos beantragt oder ist ihr Konto deaktiviert, kannst du als letzte aktive Verwaltung weder austreten noch dir das Recht entziehen lassen. Dasselbe gilt, wenn die zweite Verwaltung ein Dienstkonto ist: Es kann keine Mitglieder verwalten und zählt deshalb nicht. Übernimmt beim Kontolöschantrag die dienstälteste Leitung, kommt dafür nur eine Person in Frage, kein Dienstkonto; gibt es keine, verwaist die Organisation (REQ-024 v1.32 AK-10, AK-65).
- Behoben (API): Kalender-Apps können iCal-Feeds wieder abonnieren (Issue #2226, REQ-015). Der öffentliche Abruf-Endpunkt
GET /api/v1/calendar/feeds/{key}/feed.ics?token=…, auf den die beim Anlegen oder Erneuern eines Feeds gezeigte Abo-URL zeigt, war seit März 2026 nicht mehr eingebunden und antwortete404— kein Kalender-Abo konnte Ereignisse laden. Er ist wieder da, ohne Anmeldung (der Token in der URL ist die Berechtigung) und in beiden Betriebsmodi. Er lieferttext/calendarmitCache-Control: no-storeund nur Ereignisse des Gartens, zu dem der Feed gehört. Fehlender, unbekannter oder rotierter Token, der Token eines anderen Feeds und ein deaktivierter Feed antworten alle gleich mit404; ein Feed nach seinem Ablaufdatum mit410. Je Client-IP gilt ein Limit von 30 Abrufen pro Minute (RATE_LIMIT_CALENDAR_FEED), darüber429. Token und Feed-Schlüssel erscheinen in keiner Zeile des Zugriffs- oder Anwendungslogs. Die Abo-URL baut die API jetzt aus der Vorlage des Endpunkts selbst, sodass beide nicht mehr auseinanderlaufen können. Der Hinweis „Noch nicht implementiert“ im Benutzerhandbuch ist entfernt. - Sicherheit (API): iCal-Feeds enden mit der Mitgliedschaft, und die Antwort ist gehärtet (Issue #2226, Security-Review, REQ-015). Der Abruf
feed.icsliefert nur noch, solange der Eigentümer des Feeds aktives Mitglied des Gartens mit aktivem Konto ist und der Garten aktiv ist — sonst dieselbe404; ein Feed ohne Eigentümer liefert nie. Wird ein Mitglied entfernt oder verlässt es den Garten, löscht Kamerplanter seine Feeds dort. Die Leitung listet alle Feeds des Gartens mitGET …/calendar/feeds?scope=all(für andere Rollen403). Auch einen einzelnen Feed (GET …/calendar/feeds/{key}) siehst du nur, wenn er dir gehört oder du die Leitung bist — für jedes andere Mitglied antwortet ein fremder Feed mit404wie ein unbekannter (bisher konnte jedes Mitglied mit dem Schlüssel Name, Eigentümer und Filter eines fremden Feeds lesen). Titel und Beschreibungen werden nach RFC 5545 escaped: CR, LF, CRLF und die Unicode-Umbrüche NEL, LINE SEPARATOR und PARAGRAPH SEPARATOR werden zu einem escapten Zeilenumbruch, übrige Steuerzeichen entfernt, Zeilen auf 75 Oktette gefaltet — ein Aufgabentitel kann keine eigene Eigenschaft (etwaATTACH) mehr in den Kalender jedes Abonnenten schreiben. Der Standortfilterfilters.site_keyder Feeds ist entfernt: Er wurde gespeichert, aber nie angewendet, ein „nur Standort X“-Feed lieferte den ganzen Garten; gesendete Werte werden ignoriert, gespeicherte beim Lesen übergangen und beim nächsten Ändern des Feeds entfernt. Die Abo-URL baut die API ausAPP_BASE_URLstatt aus demHost-Header der Anfrage, mithttps://außer fürlocalhost. FehltAPP_BASE_URLdas Schemahttp:///https://oder der Host, startet das Backend trotzdem, protokolliert aber die Warnungapp_base_url_not_absolute_http(ohne den Wert); die Abo-URL ergänzt dann selbsthttps://(fürlocalhost,127.0.0.1und::1http://). Betreiberhinweis: SetzeAPP_BASE_URLin Produktion auf die vollständige öffentlichehttps://-Adresse — QR-Codes und die OAuth-Rückruf-URL ergänzen kein Schema. Eine Antwort enthält höchstens 2000 Ereignisse (Fenster 30 Tage zurück bis 90 Tage voraus), und neben dem Limit je IP gilt eines je Token (RATE_LIMIT_CALENDAR_FEED_PER_TOKEN, Standard60/hour). Betreiberhinweis: Ein Ingress mit aktivem Access-Log protokolliert die Abo-URL samt Token — siehe Benutzerhandbuch Kalender. - Sicherheit / Upgrade-Hinweis (API): Die Token der iCal-Feeds liegen nicht mehr im Klartext in der Datenbank (Issue #2171, REQ-015). Bisher stand der Token jedes Kalender-Feeds unverschlüsselt in
calendar_feeds.token, und jede Abfrage eines Feeds (GET /api/v1/t/{slug}/calendar/feeds,GET …/feeds/{key},PUT …/feeds/{key}) gab ihn samt Abo-URL zurück — wer die Datenbank oder eine Sicherung lesen konnte, konnte jeden Feed abrufen. Jetzt speichert Kamerplanter nur noch den SHA-256-Hash (token_hash). Token undical_urlstehen nur noch in der Antwort vonPOST …/calendar/feedsundPOST …/feeds/{key}/regenerate-token, also genau einmal; alle anderen Antworten enthalten wedertokennochical_url. Eine verlorene URL ersetzt du durch eine Token-Erneuerung. Migrationv0090hasht beim ersten Start die vorhandenen Token und entfernt den Klartext: Bereits verteilte URLs lösen weiter ihren Feed auf. Gemessen dabei: Der öffentliche Abruf-EndpunktGET /api/v1/calendar/feeds/{key}/feed.ics, auf den die Abo-URL zeigt, war seit März 2026 nicht eingebunden (404); Issue #2226 bindet ihn wieder ein (eigener Eintrag). Kein Image-Rollback unterv0090: Ein älteres Image legt den alten eindeutigen Index auftokenwieder an und startet nicht, sobald zwei Feeds keinen Klartext-Token mehr haben. API-Clients, dietokenoderical_urlaus einer Lese-Antwort genutzt haben, müssen auf die Antwort beim Anlegen oder Erneuern umstellen.PUT …/feeds/{key}undPOST …/feeds/{key}/regenerate-tokenstehen jetzt nur dem Mitglied zu, dem der Feed gehört, und der Leitung; für jedes andere Mitglied antworten sie404(bisher konnte jedes Mitglied ab Gärtner den Feed eines anderen umbenennen oder dessen URL erneuern). EinPUTschreibt den Token-Hash nicht mehr zurück, sodass er eine gleichzeitige Rotation nicht rückgängig machen kann. Rolling Update: Ein Feed, den ein noch laufender Pod des alten Images anlegt oder rotiert, nachdemv0090gelaufen ist, behält seinen Klartext-Token — die Migration läuft nur einmal. Skaliere das alte Deployment vor dem Update herunter, oder prüfe danach mitFOR f IN calendar_feeds FILTER HAS(f, "token") COLLECT WITH COUNT INTO n RETURN nund lass die betroffenen Feeds rotieren. - Sicherheit (Mandantentrennung): Die selbst gehostete Pflanzenerkennung nennt dir keine Art mehr, die einem anderen Garten gehört (Issue #2173). Bisher konnte ein Treffer aus dem gemeinsamen Referenzbild-Index den Schlüssel und den wissenschaftlichen Namen einer privaten Art eines anderen Gartens enthalten. Jetzt bekommst du nur Treffer zu globalen Arten, deinen eigenen und solchen, die mit deinem Garten geteilt sind — bei der Bestimmung und bei der Fotoqualitäts-Bewertung. Das gilt auch für Einträge, die schon im Index stehen. Die Referenzbild-Beschaffung indiziert nur noch globale Arten; für die Art eines Gartens antwortet
POST /api/v1/admin/reference-images/acquire/{species_key}mit404. Der Inferenz-Service lehnt einen Nutzerbeitrag (source=user_contributed) ohneis_active=falseoder ohnecontributed_by/tenant_keymit422ab. - Behoben (API): Bearbeiten setzt keine Felder mehr zurück, die das Formular nicht enthält. Ein bearbeiteter System-Standorttyp (z. B. „Gewächshaus“) verlor sein System-Kennzeichen und ließ sich danach löschen; jetzt bleibt er ein Systemtyp, und das Löschen antwortet weiter mit
422. Eine bearbeitete Substratmischung bleibt eine Mischung mit ihren Bestandteilen, statt zu einem einfachen Substrat zu werden. Bearbeitest du eine Art, bleiben ihre Eigenschaften (traits), die Schnittmonate und die Eignung als Gründüngung erhalten; eine bearbeitete Wachstumsphase bleibt wiederkehrend und behält die Herkunftsangabe ihres Kc-Werts. Diese Felder pflegt der Server — ein Wert im Anfragekörper wird weiterhin ignoriert. - Geändert (Berechtigung): Welche Wetterquellen ein Standort in welcher Reihenfolge nutzt, legt jetzt nur noch die Leitung fest — so, wie es das Rollenmodell vorsieht. Als Gärtner siehst du die Auswahl weiter, kannst sie aber nicht mehr ändern und keine Quelle testen (
PUT /api/v1/t/{slug}/sites/{key}/weather-sourceundPOST …/weather-sources/testantworten403). In der Oberfläche ist der Abschnitt „Wetterquelle“ für dich dann schreibgeschützt (Issue #2181). - Behoben: Das Bearbeiten eines Standorts, eines Bereichs oder eines Stellplatzes setzte Werte zurück, die der Server selbst pflegt. Ein bearbeiteter Standort verlor die Liste seiner aktiven Wetterquellen (
weather_source_priority). Ein bearbeiteter Bereich hatte danach Tiefe0und keinen Pfad und erschien im Standortbaum wie ein oberster Bereich. Ein bearbeiteter, belegter Stellplatz galt danach als frei und konnte bei der nächsten Belegung erneut vergeben werden. Diese Werte bleiben jetzt erhalten; Tiefe und Pfad eines Bereichs werden wie beim Anlegen aus dem übergeordneten Bereich abgeleitet (Issue #2181). - Behoben (Einladungen), Issue #2162: Eine E-Mail-Einladung in einen Garten wurde nie verschickt — die Einstellungsseite meldete „Einladung gesendet“ und verwarf den Link. Jetzt geht nach dem Anlegen eine E-Mail mit dem Annahme-Link (
{FRONTEND_URL}/invitations/accept?token=…, 7 Tage gültig) an die eingeladene Adresse; sie nennt weder Garten noch Einladenden. Geändert (API):POST /api/v1/tenants/{slug}/invitations/emailund…/invitations/linkliefern zusätzlichaccept_url, die E-Mail-Einladung außerdemdelivered. Beidelivered: false(kein zustellender E-Mail-Adapter — auchEMAIL_ADAPTER=consoleohneDEBUG—, Versand abgewiesen oder Transportfehler) besteht die Einladung trotzdem; die Seite sagt das und zeigt den Link zum Weitergeben. Ein Einladungslink kopiert jetzt den Link statt des nackten Tokens. Wer einen Einladungslink ohne Sitzung öffnet, landet nach der Anmeldung — auch über einen Anmeldeanbieter — wieder auf der Annahme-Seite; der Link zur Registrierung übernimmt die Einladung. Begrenzt:POST …/invitations/emailerlaubt je KontoRATE_LIMIT_INVITATION_EMAIL(Standard10/minute) und höchstensTENANT_INVITATION_EMAILS_PER_DAYE-Mail-Einladungen in 24 Stunden (Standard50, über alle Mandanten); darüber429 RATE_LIMIT_EXCEEDED, nichts wird angelegt oder verschickt. Eine Einladung, die nicht mehr angenommen werden kann (#2180), öffnet auch die Registrierung beiinvite_onlyoder Domain-Liste nicht mehr. - Sicherheit (Einladungen), Issue #2180: Das Annehmen einer Einladung prüft die Rangregel erneut (REQ-024 AK-58): Eine Leitungs-Einladung in den Mandanten
platform, deren Absender dort nicht (mehr) Leitung ist, antwortet mit403und legt nichts an. Vorher machte eine solche Einladung — ausgestellt vor der Regel (#2084) von einem Mitglied mit Verwaltung, aber ohne Leitung — den Annehmenden zum Plattform-Admin. Migrationv0089widerruft beim Update jede offene Leitungs-Einladung inplatformund protokolliert nur ihre Anzahl (revoke_platform_lead_invitations_applied). Wer eine solche Einladung offen hatte, muss neu eingeladen werden — von einer Plattform-Leitung. Bereits angenommene Einladungen dieser Art ändert die Migration nicht — wie du solche Plattform-Leitungen findest und sichtest, steht unter Plattform-Admin-Bereich. - Sicherheit (Mandantentrennung): Der Aktivitäten-Katalog (
GET /api/v1/activities,GET /api/v1/activities/{key}) zeigt dir die globalen Aktivitäten und die deines aktiven Gartens, nie die eines anderen Gartens; ein fremder Schlüssel antwortet „nicht gefunden“. Ein automatisch erzeugter Aktivitätsplan, den alle Gärten teilen, entsteht nur noch aus globalen Aktivitäten (Issue #2119). Heute legen nur Plattform-Admins Aktivitäten an, und alle sind global — für dich ändert sich nichts. - BREAKING (API): Wissensfrage nur noch im Garten und hinter der KI-Freigabe (Issue #2175, REQ-031 v2.5, Szenario 15).
POST /api/v1/knowledge/askist entfernt (kein Alias,404). Die Frage an die Wissensbasis heißt jetztPOST /api/v1/t/{slug}/ai/knowledge/ask— gleicher Response-Body; im Request gilt jetzttop_k≤ 10 (vorher 20) und der Pflanzenkontext ist begrenzt (species,phase,substrateje höchstens 100 Zeichen,ec0–20,ph0–14, sonst422). Die alte Route beantwortete jedem angemeldeten Konto, auch Betrachtern und Service Accounts, eine Frage mit einem Sprachmodell-Aufruf: ohne KI-Schalter, ohne Einwilligung, ohne Tagesbudget. Die neue Route prüft wie „Warum?“ und Chat ab Rolle Gärtner (Betrachter403), den Betreiber-SchalterAI_FEATURES_ENABLED(404), die KI-Einstellung des Gartens (403 ai.disabled_for_tenant), die eigene Einwilligungai_knowledge_question(403 CONSENT_REQUIRED; mit Pflanzenwerten imcontextzusätzlichai_tenant_data_access; antwortet der Knowledge Service mit einem Cloud-Modell der Plattform, zusätzlichai_cloud_processingund die Cloud-Freigabe des Gartens, sonst403 ai.disabled_for_tenant— maßgeblich ist der System-Standard-Provider, nicht der Provider des Gartens) und das KI-Tagesbudget (429 AI_BUDGET_EXCEEDED) — alles, bevor der Knowledge Service angefragt wird. Das MinutenbudgetRATE_LIMIT_INFERENCEje Konto bleibt. Ist der Knowledge Service nicht erreichbar, antwortet sie502. Im Light-Modus gibt es keine Einwilligung; dort ist die WissensfragePOST /api/v1/public/ai/ask.GET /api/v1/knowledge/search(ohne Sprachmodell) bleibt unverändert. - BREAKING (API):
/api/v1/public/ai/*nur noch im Light-Modus (REQ-031 v2.6 §5.3, REQ-027 v1.8).POST /api/v1/public/ai/askundGET /api/v1/public/ai/healthwaren in beiden Modi ohne Anmeldung erreichbar — im Full-Modus beantwortete die Frage-Route jedem anonymen Aufrufer eine Frage mit einem Sprachmodell-Aufruf, an Einwilligung, KI-Schalter des Gartens und Tagesbudget vorbei. Im Full-Modus existieren beide Pfade nicht mehr (404); die Wissensfrage ist dortPOST /api/v1/t/{slug}/ai/knowledge/ask. Im Light-Modus verlangen sie jetzt einen angemeldeten Principal, den der Light-Modus ohne Login als System-User liefert — für die Light-Oberfläche ändert sich nichts. Das Adress-LimitAI_PUBLIC_RATE_LIMIT_PER_MINund der Betreiber-SchalterAI_FEATURES_ENABLEDgelten weiter. - Datenschutz (Referenzbild-Beitrag, Issue #2174, REQ-034 v1.4 AC-17): Ein Identifikationsfoto als Referenz für die selbst-gehostete Erkennung beizusteuern (
POST /api/v1/t/{slug}/identification/reference) setzt jetzt die Einwilligung „Foto-Beitrag zur Pflanzenerkennung“ (reference_contribution) voraus — wie schon der automatische Beitrag aus der Galerie. Ohne erteilte oder nach widerrufener Einwilligung antwortet die API mit403(CONSENT_REQUIRED); dabei werden weder dein Tages-Kontingent verbraucht noch ein Beitrag vermerkt oder das Bild verarbeitet. Die Art wird nur noch unter deinem Mandanten aufgelöst: Eine private Art eines anderen Mandanten antwortet wie eine unbekannte mit404. Verhaltensänderung: Im Light-Modus ist der Beitrag nicht mehr möglich und antwortet mit409(ADAPTER_NOT_AVAILABLE) — dort gibt es keine Einwilligungsverwaltung, und REQ-034 schaltet den Referenz-Beitrag im Light-Modus ab. - Datenschutz (Schädlingserkennung, REQ-044 v1.3/v1.4, REQ-010 v1.6/v1.7): Ein von einer Plattform-Admin global freigegebenes Schädlingsfoto fließt nur noch dann als Referenz in den Erkennungs-Index ein, wenn die Person, die es beigetragen hat, die eigene Einwilligung „Foto-Beitrag zur Schädlingserkennung“ (
pest_reference_contribution, REQ-044 v1.4, REQ-010 v1.7) erteilt hat. Die Einwilligung „Foto-Beitrag zur Pflanzenerkennung“ (reference_contribution) nennt nur Galerie-Fotos von Pflanzen und öffnet den Schädlingsindex nicht; bestehende Einwilligungen dafür werden nicht übernommen — wer beiträgt, muss neu einwilligen. Ohne erteilte oder nach widerrufener Einwilligung — und immer im Light-Modus — bleibt das Foto freigegeben und sichtbar, der Hintergrund-Task endet aber ohne Aufruf des Inferenz-Dienstes (no_consentbzw.light_mode). Ein Widerruf entfernt bereits indexierte Vektoren nicht; das tut das Löschen des Fotos oder des Kontos. - Geändert (API): Glossar-Erklärung erzeugen nur mit KI-Freischaltung des Gartens (REQ-035 v1.4).
POST /api/v1/t/{slug}/glossary/term/{slug}/generateverlangt zusätzlich zur Rolle die Garten-Freischaltung (REQ-031 Stufe 2): ausgeschaltet →403(AI_DISABLED_FOR_TENANT), bevor Zwischenspeicher, Tageskontingent oder Sprachmodell berührt werden. Verhaltensänderung: Bei instanzweit abgeschalteter KI antwortet die Route jetzt mit404statt mit der kuratierten Kurzdefinition. Lesen (GET …/glossary/terms,GET …/glossary/term/{slug}, öffentliche und Admin-Routen) ist unverändert. - Knowledge Service, Sicherheit (Issue #2176): Ein unbekannter Wert in
LLM_PROVIDERfiel bisher still auf den Anthropic-Adapter zurück — ein Tippfehler wieopenai-compatibleschickte jede Frage samt des für den lokalen Server gedachtenLLM_API_KEYan die Cloud. Jetzt startet der Knowledge Service mit einem unbekannten Wert nicht und nennt die erlaubten Werte (anthropic,ollama,openai_compatible). - Sicherheit (Mandantentrennung, Hygiene-Paket, Issue #2144): Eine Reihe kleiner Lücken aus dem Mandanten-Audit ist geschlossen. Jede Antwort unter
/api/v1/trägt jetztCache-Control: no-store, wenn die Route nichts anderes festlegt — Anhänge, Schädlingsbilder und der Chat-Stream behalten ihre eigene Angabe. „Substrat-Charge für Wiederverwendung vorbereiten" verlangt mindestens die Rolle Gärtner (Beobachter:403). Das Ändern der Zusatzberechtigungen eines Mitglieds verlangt dieselbe Bestätigung wie ein Rollenwechsel. Als Plattform-Admin kannst du dein eigenes Konto und das letzte Konto mit Plattform-Rolle nicht mehr deaktivieren (422). Ein Kommentar-Schlüssel, der nicht zur angegebenen Aufgabe gehört, antwortet404statt422; der CSV-Import sieht beim Abgleich nur die Arten, die du auch in der Artenliste siehst. Eine Benachrichtigung ohne Empfänger wird nicht mehr gespeichert und ist für niemanden abrufbar. Mischkultur-Listen, -Zähler und -Empfehlungen zeigen nur Arten, die du sehen darfst; Mischkultur-Kanten verbinden nur globale Arten. Filter-Feldnamen in Datenbankabfragen werden geprüft, bevor eine Abfrage entsteht. - Geändert (MCP): Ein
idempotency_key, der mit anderen Argumenten erneut gesendet wird, antwortet mitconflict.idempotency_key_reused(409am REST-Alias) statt das Ergebnis des ersten Aufrufs zu wiederholen; ein abgelaufener Eintrag wird nicht mehr wiederholt (Issue #2144). - Neu (Betrieb):
python -m app.migrations.audit_legacy_stampslistet nur lesend Zeilen, deren Autor kein Mitglied ihres Gartens ist — Spuren der frühen Mandanten-Nachrüstung (v0004). Migrationv0088entfernt diesen Stempel aus Schädlingen, Krankheiten und Behandlungen. Details:src/backend/app/migrations/README.md(Issue #2144). - Neu (API): Service Accounts für einen Garten (Issue #2137, interne Referenz MT-041). Wer in einem Garten Leitung ist und die Zusatzberechtigung Technik hat, legt mit
POST /api/v1/t/{slug}/service-accountsein Konto für eine Maschine an (Home Assistant, Grafana, CI/CD) — ohne Passwort, alsviewerodergrower, mit einem ersten API-Key, der nur in diesem Garten gilt. Dazu Liste, Key-Rotation (…/{sa_key}/rotate-key, alte Keys sofort widerrufen oder nach einem Übergangsfenster von bis zu 24 h) und Entfernen (DELETE …/{sa_key}). Jede Änderung verlangt dein Passwort (Step-upservice_account_change) und steht im Sicherheitsprotokoll. HöchstensTENANT_MAX_SERVICE_ACCOUNTS(Default20) je Garten; ein Service Account belegt einen Platz im Mitgliederlimit. Light-Modus: nicht verfügbar. Siehe Service Accounts & API-Keys - Neu (API): Ein API-Key bekommt beim Ausstellen eine IP-Allowlist (
ip_allowlist, CIDR, nicht weiter als/8bzw./32), ein Ratenlimit (rate_limit_per_minute, 1–10000) und ein Ablaufdatum (expires_at, höchstens 730 Tage) — fürPOST /api/v1/auth/api-keyswie für Service Accounts. Durchgesetzt wurden die drei Werte schon, setzen ließen sie sich bisher nicht. Ein unbrauchbarer Wert ist422, bevor nach dem Passwort gefragt wird; die Key-Liste zeigt die Werte (Issue #2137) - Geändert (API): Ein Service Account kann keinen Garten gründen (
POST /api/v1/tenants) und keine Einladung annehmen (403); die Mitgliederverwaltung macht ihn nicht zur Leitung (403). Service Accounts verwaltet jetzt Leitung und Technik statt der Zusatzberechtigung Verwaltung (Issue #2137) - Sicherheit (Mandantentrennung): Schreibende Operationen auf Standorte, Bereiche, Stellplätze und Nährstoffpläne prüfen die Zugehörigkeit zum Mandanten jetzt im Dienst selbst und nicht mehr nur im aufrufenden Endpunkt (Issue #2107). Für dich ändert sich nichts; ein fremder Schlüssel antwortet weiter mit „nicht gefunden“. Das Entfernen eines Nährstoffplans von einer Pflanze und das Ändern oder Löschen einer Aktivität im gemeinsamen Katalog werden ebenfalls im Dienst geprüft (Issue #2120). Migration
v0087entfernt das nie befüllte Feldtenant_keyaus gespeicherten Bereichen und Stellplätzen. Dasselbe gilt jetzt für Pflanzdurchläufe (Anlegen, Ändern, Löschen, Einträge, Pflanzen anlegen, übernehmen und lösen, Phasenwechsel, Phasendaten, Sammelentfernen, Nährstoffplan entfernen) und Tanks (Löschen, Zustand, Wartung, Wartungsplan, Befüllung). Ebenso für Düngeereignisse, Gießprotokolle und Gießereignisse (Ändern, Löschen, Anlegen), Erntechargen (Ändern, Qualität, Ertrag, Ernte abschließen), Sukzessionspläne (Ändern, Löschen, Durchläufe erzeugen), Nachernte-Chargen, Überwinterungsprofile, Aktivitätspläne und das Bestätigen eines CSV-Imports. Schließt du die Ernte eines ganzen Durchlaufs ab, beendet das nur noch deine eigenen Pflanzen, auch wenn ein Altdatensatz den Durchlauf mit einer fremden Pflanze verknüpft. Dasselbe gilt für das Anwenden eines Aktivitätsplans auf einen Durchlauf, das Sammelentfernen, die Phasendaten und das Umsetzen der Pflanzen eines Durchlaufs, Tagebucheinträge im Durchlauf und das Bestätigen einer Bewässerung: Sie berücksichtigen nur deine eigenen Pflanzen. Beim Bestätigen einer Bewässerung wird der Nährstoffplan eines Durchlaufs nur verwendet, wenn du ihn lesen darfst. Das Umsetzen eines Durchlaufs mit Pflanzen auf Stellplätzen an einen anderen Bereich bricht nicht mehr mit einem Serverfehler ab. Die Dienste für Aktoren, Aquaponik-Systeme und InvenTree (Verbindungen, Verknüpfungen, Ausrüstung) nehmen den Mandanten jetzt in jeder Methode nur noch als benanntes Argument entgegen, sodass ein vertauschter Schlüssel nicht mehr unbemerkt bleibt; für dich ändert sich nichts. Dasselbe gilt jetzt für die Fotogalerie einer Pflanze (Foto hochladen und verknüpfen, Titelbild, Bildunterschrift und Aufnahmedatum, Qualitätsprüfung, Foto löschen). Ebenso für Arten, Sorten, Substrate, Substratmischungen und Substratchargen (Anlegen, Ändern, Löschen): Der Dienst verlangt deinen Mandanten, deine Rolle und die Plattform-Admin-Angabe jetzt immer ausdrücklich, statt ohne sie ungeprüft zu schreiben. Das Ändern und Löschen von Einträgen im Phasenverlauf einer Pflanze prüft den Mandanten jetzt im Dienst selbst. Ebenso für Pflanzen (Anlegen, Ändern, Entfernen), Behandlungsanwendungen im Pflanzenschutz, Phasen eines Workflows (Anlegen, Ändern, Löschen) und das Duplizieren eines Workflows, Vermehrungschargen (Anlegen, Abschließen), das Löschen eines Tagebucheintrags, die Winterhärtezone eines Standorts und die nächtliche Prüfung des Winterquartier-Klimas. Eine Workflow-Phase ohne zugehörigen Workflow lässt sich weiterhin nicht ändern oder löschen; die Antwort bleibt „nicht gefunden“. - BREAKING (API): KI-Aufrufe haben ein Tagesbudget pro Konto und pro Garten (Issue #2110). Jede Route, die ein Sprachmodell anspricht — Tipps und Tagestipp erzeugen, „Warum?“, Chat-Nachricht, Glossar-Erklärung erzeugen, KI-Diagnose —, zählt gegen
AI_BUDGET_USER_CALLS_PER_DAY(Standard50je Konto und Garten),AI_BUDGET_TENANT_CALLS_PER_DAY(500je Garten) undAI_BUDGET_TENANT_TOKENS_PER_DAY(2000000Tokens je Garten), jeweils pro UTC-Tag. Darüber antwortet die API mit429undAI_BUDGET_EXCEEDED(details[0].codenennt das Budget) sowieRetry-Afterbis Tagesende. Ist Valkey nicht erreichbar, läuft kein KI-Aufruf (503 AI_BUDGET_UNAVAILABLE). Zusätzlich gilt auf diesen Routen das MinutenbudgetRATE_LIMIT_INFERENCEje Konto. Das KI-Aufruf-Protokoll speichert je Aufruf die Token-Zahlen (prompt_tokens,completion_tokens), damit die Kosten eines Gartens zählbar sind. Eine abgelehnte Chat-Nachricht kommt jetzt als HTTP-Fehler statt als abgebrochener Stream; die Oberfläche erklärt jede Ablehnung. - Behoben (API): Eine
429-Antwort aus einer Grenze des Fachcodes (Tageslimits von Pflanzenbestimmung und Referenzbeitrag, API-Key-Limit, KI-Budget) trägt jetzt den HeaderRetry-After(Issue #2110). Mit #2109 bekam ihn nur die Minutenbegrenzung; die Wartezeit der übrigen stand bisher nur im Meldungstext. - Glossar: Ob eine erzeugte Glossar-Erklärung als Cloud-Verarbeitung gilt, entscheidet jetzt der Standard-Provider der Plattform, nicht der Standard des Gartens, der den Begriff zuerst angefragt hat (Issue #2110). Die Erklärung wird allen Gärten ausgeliefert; ihr Cloud-Hinweis ist jetzt für alle gleich und steht am gespeicherten Eintrag.
- Verhaltensänderung (API): Sechs Listen, die bisher jeden Eintrag lieferten, lesen jetzt ein begrenztes Fenster mit
offset(Standard0) undlimit(Standard50, höchstens200; Issue #2131, MT-035):GET /admin/platform/tenants,GET /admin/platform/users,GET /tenants/{slug}/invitations,GET /t/{slug}/ai/conversations,GET /t/{slug}/tasks/plants/{plant_key}undGET /t/{slug}/post-harvest/{key}/observations. Die Antwort bleibt ein Array; ein Client ohnelimiterhält jetzt die ersten 50 Einträge statt aller und muss weitere Seiten mitoffsetlesen. Mandanten, Nutzer, Einladungen und Unterhaltungen sind neueste zuerst sortiert, Aufgaben nach Fälligkeit, Beobachtungen neueste zuerst — bei gleichem Zeitstempel entscheidet der Schlüssel, damit keine Seite einen Eintrag doppelt liefert oder auslässt. - API:
GET /t/{slug}/plant-instances,/watering-logs,/watering-eventsund/feeding-eventsnehmen zusätzlichafter=<key>an und blättern damit per Schlüssel statt peroffset(Issue #2131). Diese vier Listen zählen außerdem nicht mehr bei jeder Seite alle Einträge des Gartens nach — die Zahl stand nie in der Antwort. Ein neuer Index(tenant_key, _key)auf den vier Collections entsteht beim nächsten Start; gemessen auf ArangoDB 3.12 mit 50 000 Einträgen eines Gartens sinkt eine Seite von 167 ms (Seite 1) bzw. 262 ms (letzte Seite per Cursor) auf etwa 4 ms bei den Pflanzen und die erste Seite eines kleinen Gartens von bis zu 132 ms auf etwa 4 ms bei Gieß- und Düngeereignissen. - API:
POST /calculations/sun-times-rangeberechnet höchstens 366 Tage pro Anfrage; ein längerer Zeitraum wird mit422abgelehnt (Issue #2131). Bisher rechnete ein Aufruf jeden Tag zwischen beliebigen Daten. - Verhaltensänderung (API): Sechs weitere Listen lesen jetzt ein begrenztes Fenster mit
offset(Standard0) undlimit(Standard50, höchstens200; Issue #2131, MT-035):GET /tenants/{slug}/members,GET /tenants/{slug}/assignments,GET /admin/platform/tenants/{tenant_key}/members,GET /t/{slug}/tasks/workflows/{key}/executions,GET /t/{slug}/inventree/referencesundGET /t/{slug}/propagation/stats/by-cultivar. Die Antwort bleibt ein Array; ein Client ohnelimiterhält die ersten 50 Einträge statt aller. Mitglieder und Zuweisungen sind älteste zuerst sortiert, Durchläufe neueste zuerst, Verknüpfungen nach Collection, die Statistik nach Sorten-Schlüssel — bei Gleichstand entscheidet der Schlüssel. Zwei neue Indizes entstehen beim nächsten Start:(tenant_key, created_at)auflocation_assignmentsundworkflow_template_keyaufworkflow_executions; gemessen auf ArangoDB 3.12 mit 20 000 Einträgen las die erste Seite vorher die ganze Collection, jetzt nur die Einträge des Gartens bzw. der Vorlage. - API: Dreizehn Listen eines Gartens zählen nicht mehr bei jeder Seite alle passenden Einträge nach — die Zahl stand nie in der Antwort (Issue #2131, MT-035):
GET /t/{slug}/tasks,GET /t/{slug}/planting-runs,GET /t/{slug}/planting-runs/{key}/diary,GET /t/{slug}/planting-runs/{key}/plants/{plant_key}/diary,GET /t/{slug}/plant-instances/{key}/diary,GET /t/{slug}/ipm/plants/{plant_key}/inspections,GET /t/{slug}/ipm/plants/{plant_key}/treatment-applications,GET /t/{slug}/harvest/plants/{plant_key}/observations,GET /t/{slug}/harvest/batches,GET /t/{slug}/post-harvest,GET /t/{slug}/propagation/events,GET /t/{slug}/propagation/batchesundGET /import/jobs. Antwort, Fenster und Filter bleiben gleich; bei gleichem Zeitstempel entscheidet jetzt der Schlüssel über die Reihenfolge, damit keine Seite einen Eintrag doppelt liefert oder auslässt. - API: Fünfzehn weitere Listen zählen nicht mehr bei jeder Seite alle passenden Einträge nach — die Zahl stand nie in der Antwort (Issue #2131, MT-035):
GET /activities,GET /ipm/pests,GET /ipm/diseases,GET /ipm/treatments,GET /phase-definitions,GET /phase-sequences,GET /t/{slug}/sites,GET /t/{slug}/tanks,GET /t/{slug}/fertilizers,GET /t/{slug}/nutrient-plans,GET /t/{slug}/succession-plans,GET /t/{slug}/overwintering-profiles,GET /t/{slug}/propagation/protocols,GET /t/{slug}/tasks/workflowsundGET /t/{slug}/harvest/indicators. Dasselbe gilt für das MCP-Werkzeugget_harvest_readiness, die Inspektions-Abfrage der Diagnose-Werkzeuge und den Inspektionsplan einer Pflanze. Antwort, Fenster und Filter bleiben gleich; bei gleichem Namen entscheidet bei Nährstoffplänen, Bewurzelungsprotokollen, Phasen-Definitionen und Workflow-Vorlagen jetzt der Schlüssel über die Reihenfolge, damit keine Seite einen Eintrag doppelt liefert oder auslässt. - Behoben (Datenschutz): Sechs Aufräum-Tasks liefen seit ihrer Einführung (Juli 2026) bei jedem Aufruf mit
TypeErrorauf und löschten nichts (Issue #2150, NFR-011):ai.cleanup_expired_conversations,ai.cleanup_expired_audit_log,glossary.cleanup_expired_cache,glossary.invalidate_after_reingest,mcp.cleanup_expired_audit_log,mcp.cleanup_expired_idempotency. Sie öffnen ihre Datenbankverbindung jetzt wie alle anderen Tasks überget_db(). Nach dem Update holen die ersten Läufe den Rückstand nach: KI-Audit-Einträge älter als 30 Tage, abgelaufene Glossar-Cache-Zeilen und MCP-Idempotenz-Datensätze älter als 24 Stunden werden gelöscht. Weil der Idempotenz-Datensatz bisher nie verschwand, lieferte ein MCP-Schreibwerkzeug mit wiederverwendetemidempotency_keyauch nach mehr als 24 Stunden das gespeicherte Ergebnis statt neu auszuführen; jetzt gilt wieder das Fenster aus REQ-033 von 24 Stunden — ein abgelaufener Datensatz wird nicht mehr wiederholt, auch bevor der stündliche Lauf ihn löscht (Issues #2144, #2169). KI-Unterhaltungen (90 Tage) und das MCP-Audit (90 Tage) hatten noch keine fälligen Einträge. - Datenschutz: Der persönliche Datenexport (Art. 15/20) enthält jetzt deinen persönlichen Garten (Issue #2135): Standorte mit GPS-Koordinaten, Beete, Stellplätze, Pflanzen, Pflanzdurchläufe, alle Aufgaben, das Tagebuch und Anhang-Metadaten — begrenzt auf die persönlichen Gärten, die dir gehören, ohne Kontokennungen anderer Mitglieder. Aus Organisationen bleiben wie bisher nur die Mitgliedschaft und deine eigenen Beiträge im Export. Sensor-Messwerte sowie Tank-, Gieß- und Düngeprotokolle sind noch nicht enthalten.
- Datenschutz: Eine Kontolöschung lässt keine Organisation mehr ohne Verwaltung zurück (Issue #2134). War die Person die letzte mit Verwaltungsrecht, übernimmt die dienstälteste Leitung die Verwaltung (Sicherheits-Audit
via: account_erasure, die Mitglieder werden benachrichtigt). Bleibt keine Leitung oder war sie das einzige Mitglied, wird die Organisationorphanedund nach der Frist der Mandantenlöschung gelöscht; Mitglieder und Platform-Admins werden benachrichtigt. Bisher blieb eine solche Organisation unbefristet und unverwaltbar bestehen. Die Löschvorschau (GET /privacy/erasure-preview,GET /admin/platform/users/{key}/erasure-preview) nennt beide Fälle im neuen Feldorganizations. Die in REQ-023 §5a.5 skizzierte Notfall-Verwaltung entfällt. - BREAKING (API): Eine Mandantenlöschung wird jetzt geplant statt sofort ausgeführt (Issue #2123, REQ-024 AK-52).
DELETE /tenants/{slug}undDELETE /admin/platform/tenants/{key}antworten weiter mit202, aber mitstatus: "scheduled"undscheduled_for: Der Mandant ist bis dahin für alle gesperrt, seine Mitglieder erhalten eine E-Mail mit dem Datum, und erst nachRETENTION_TENANT_ERASURE_GRACE_DAYSTagen (Standard 90,0= sofort wie bisher) löscht der tägliche Lauf. Bis dahin brichtPOST /tenants/{slug}/erasure/cancel(Leitung und Verwaltung, Passwort) oderPOST /admin/platform/tenants/{key}/erasure/cancel(Platform-Admin) die Löschung ab. Mandanten haben dazu einen Zustandstatus(active,suspended,pending_deletion,orphaned,deleted) statt des Feldsis_active; Migrationv0085stellt bestehende Mandanten um,is_activebleibt in den Antworten als abgeleiteter Wert erhalten. Ein Platform-Admin kann einen Mandanten mit geplanter Löschung nicht mehr über den Aktiv-Schalter reaktivieren (422) — dafür gibt es den Abbruch. - Sicherheit (Sitzungen): Refresh-Tokens bilden jetzt je Anmeldung eine Familie (Issue #2116, MT-019, REQ-023 §3.2a). Wird ein bereits rotiertes Refresh-Token erneut vorgelegt, endet die ganze Familie; der Vorfall wird als
auth.refresh_replay_detectedprotokolliert. Innerhalb von 60 Sekunden erhält derselbe Client (gleicherUser-Agent) auf dem Cookie-Weg stattdessen ein Access Token ohne neuen Cookie (zwei Tabs erneuern gleichzeitig). Der Body-Weg gekoppelter Geräte hat kein Gnadenfenster. Rotierte Tokens bleiben bis zu ihrem eigenen Ablauf gespeichert, damit auch eine späte Wiederverwendung erkannt wird. Access Tokens enden mit ihrer Sitzung: Sie tragen die Token-Generation des Kontos (Claimgen); jeder Widerruf — Abmelden, alle Sitzungen abmelden, eine Sitzung beenden, Passwortänderung oder -Reset, Deaktivierung, Art.-17-Antrag, E-Mail-Wechsel und dessen Rücknahme, erkannte Wiederverwendung — erhöht sie, sodass ein vorher ausgestelltes Token ab der nächsten Anfrage mit401abgelehnt wird, statt seine 15 Minuten auszuschöpfen (REQ-023 AK-14). Das Beenden einer Sitzung beendet deshalb auch die Access Tokens der übrigen Sitzungen des Kontos; diese erneuern sich unbemerkt. Kein zusätzlicher Datenbankzugriff je Anfrage (das Konto wird ohnehin gelesen). Eine Deaktivierung durch den Admin beendet jetzt ebenfalls alle Sitzungen. Vor dem Update ausgestellte Tokens bleiben bis zu ihrem Ablauf gültig; keine Migration, kein erzwungenes Neuanmelden. - Sicherheit: Passwort-Reset- und E-Mail-Bestätigungs-Tokens werden nur noch als SHA-256-Hash gespeichert (
password_reset_token_hash,email_verification_token_hash; Issue #2158). Vorher war der gespeicherte Wert der Link selbst; wer die Datenbank oder ein Backup lesen konnte, konnte innerhalb der Reset-Stunde ein Konto übernehmen. Migrationv0086hasht die gespeicherten Werte an Ort und Stelle; vor dem Update verschickte Links funktionieren noch genau einmal. - Web-Push: Ein Endpunkt gehört immer nur einem Konto. Wird er abonniert, verschwindet er bei allen anderen Konten (Issue #2117) — auf einem gemeinsam genutzten Gerät erhält der vorherige Nutzer darüber keine Pushes mehr.
- BREAKING (API), Sicherheit: Kamerplanter ist mit einer Home-Assistant-Instanz verbunden (der des Betreibers) und nutzt sie für alle Mandanten — dokumentiert als „Single-Household-Instanz" (Issue #2112, MT-015, REQ-005 §4c, REQ-018, REQ-030). Welche Entitäten dieser Instanz ein Mandant nutzen darf, legt jetzt der Plattform-Admin je Mandant fest (neue Collection
tenant_ha_entity_grants, Endpunkte/api/v1/admin/ha-entity-grants/tenants/{tenant_key}für Liste, Inventur, Freigeben und Zurückziehen). Sensor, Aktor, HA-Wetterquelle und HA-Benachrichtigungsziel mit nicht freigegebener Entität →422 HA_ENTITY_NOT_GRANTED(wertfrei).…/sensors/live, Tank-Live-Status, Frostwarnung, Ingest, Tank-Sync, Aktor-Sync, Regelkreis und Wetterabruf lesen nicht freigegebene Entitäten nicht mehr (im Log nur gezählt); ein nicht freigegebener Aktor fällt auf die manuelle Aufgabe zurück; HA-Benachrichtigungen gehen nur an Ziele, die dem Mandanten der Benachrichtigung freigegeben sind — auch der Standardnotify.notify. Breaking:GET /t/{slug}/ha/weather-entities,/ha/sensor-entitiesund/tanks/ha-entitiesverlangen jetzt den Scopetechnical(vorher jedes Mitglied, auch Beobachter) und liefern wieactuators/integrations/home-assistant/entitiesnur freigegebene Entitäten. Migrationv0084legt die Collection an und gibt jede bereits referenzierte Entität dem referenzierenden Mandanten frei, damit nach dem Update nichts stehen bleibt. Im Light-Modus (ein Haushalt, kein Admin-Bereich) gibt das Eintragen einer Entität sie frei. - Neu (Betrieb): Wer ein Konto anlegen darf, ist jetzt einstellbar (Issue #2132, MT-036, REQ-023 §3.2d).
REGISTRATION_MODE=open(Default — keine Änderung für bestehende Installationen),invite_onlyoderclosed, dazu optionalREGISTRATION_ALLOWED_DOMAINS(kommagetrennte E-Mail-Domains). Es gilt fürPOST /api/v1/auth/registerund für die erste Anmeldung über einen OIDC-Anbieter. Eine offene E-Mail-Einladung für genau diese Adresse ist die Ausnahme voninvite_onlyund von der Domain-Liste — lokal mit ihrem Token (invitation_tokenim Body), über OIDC mit der vom Anbieter bestätigten Adresse;closedlässt niemanden zu, auch nicht mit Einladung. Eine Ablehnung ist403(REGISTRATION_NOT_ALLOWED), für vergebene wie freie Adressen gleich.GET /api/v1/modemeldetregistration: {mode, domain_restricted}. Bestehende Konten melden sich in jedem Modus an - BREAKING (API): Das Mitgliederlimit eines Mandanten wirkt jetzt (Issue #2133, MT-037, REQ-024 AK-64). Ist ein Mandant voll, antworten die Einladungsannahme (
POST /api/v1/tenants/invitations/accept) und das Hinzufügen durch einen Plattform-Admin mit422(MEMBER_LIMIT_REACHED); die Einladung bleibt offen. Wirksam istmin(max_members, TENANT_MAX_MEMBERS_CEILING)— die neue Plattform-Obergrenze, Default50.max_membersüber der Obergrenze wird beim Anlegen und Ändern mit422abgelehnt; eine Organisation ohne Angabe erhält die Obergrenze. Ein persönlicher Mandant hat das Limit1: Wer jemanden in seinen persönlichen Garten einladen will, hebt das Limit vorher an (PATCH /api/v1/tenants/{slug}mitmax_members). Bestehende Mitglieder bleiben in jedem Fall erhalten, auch wenn ein Mandant heute mehr hat als sein Limit — nur der nächste Beitritt wird verweigert. Keine Migration -
BREAKING (API, Datenschutz): Die Einwilligungszwecke
hibp_checkundexternal_enrichmentwerden nicht mehr angeboten (Issue #2136, MT-040, REQ-025 v1.31 §3.6) — kein Code las sie: Eine HaveIBeenPwned-Prüfung gibt es nicht, und die GBIF/Perenual-Anreicherung sendet nur Artnamen.GET /api/v1/privacy/consentsund die Datenschutzerklärung listen sie nicht mehr;POST /api/v1/privacy/consentsundDELETE /api/v1/privacy/consents/{purpose}antworten für sie mit422. Gespeicherte Einwilligungen bleiben unverändert (Export, Löschung, R-04 wie bisher; keine Migration). Der Zweckerror_trackingheißt jetzt „Fehlerberichte meinem Konto zuordnen (Sentry)" und wirkt: Ein Fehlerbericht trägt die Konto- und Mandanten-Pseudonyme nur mit dieser Einwilligung. Der Hinweis im Einwilligungs-Banner nennt HaveIBeenPwned und Stammdatenanreicherung nicht mehr. -
Fehler-Tracking: Ein Ereignis einer authentifizierten Anfrage trägt im Nutzer-Block die Pseudonyme
id=sub_…undtenant=ten_…(Issue #2129, NFR-011 v1.37). Was das Sentry-SDK oder eine Integration selbst als Nutzer setzt, wird verworfen — auch in inference- und knowledge-service (gemeinsames Modulkp_errortracking). -
Betrieb (Logs, Celery, MCP): Logzeilen einer Anfrage tragen jetzt neben
request_iddie Pseudonymeactor(Konto,sub_…) undtenant(Mandant,ten_…), sobald die Anfrage authentifiziert bzw. ihr Mandant aufgelöst ist (Issue #2130, MT-034, NFR-011 v1.37). Ein aus der Anfrage gestarteter Celery-Task übernimmtrequest_id,actorundtenantüber Nachrichten-Header (kp_request_id,kp_actor,kp_tenant— nur die Pseudonyme, nie Schlüssel) und bindet sie zusammen mittask_idan jede seiner Logzeilen; nach dem Task werden sie wieder gelöst. Die MCP-Audit-Zeile (mcp_audit_log) trägt zusätzlichapi_key_ref(gesalzene Referenz auf den verwendeten Schlüssel),client_ip_ref(gekürzt wie R-03),request_idundentity_keys(die Datensatzschlüssel aus den*_key-Feldern der Werkzeug-Eingabe); alle vier stehen im Art.-15-Export (REQ-033 v1.9). Eine explizit gesetztetenant=-Angabe einer Zeile hat Vorrang. Dashboards, die Anfragen pro Mandant zählen wollen, können jetzt auftenant=filtern. - Sicherheit: Der Home-Assistant-Token, der Pl@ntNet-API-Key und die Apprise-URLs werden verschlüsselt gespeichert (Fernet mit
FERNET_KEY, wie schon der OpenWeatherMap-Key; Issue #2113). Bisher lagen sie im Klartext insystem_settingsbzw.notification_preferences— eine Apprise-URL enthält den Bot-Token oder das Webhook-Geheimnis. Neue Werte werden beim Speichern verschlüsselt, bestehende einmal durch die Migration v0083 beim Start; ein Lesezugriff schreibt nie. OhneFERNET_KEY(nur mitDEBUG=truestartbar) bleiben sie im Klartext, das Log meldetencryption_disabled. Geändert (API):GET/PUT /api/v1/t/{slug}/notifications/preferencesliefern Apprise-URLs nur noch als Platzhalter (tgram://****#1: Schema und Position). Schickst du einen Platzhalter unverändert zurück, bleibt die gespeicherte URL erhalten; ein Platzhalter ohne passende gespeicherte URL wird mit422(APPRISE_URL_MASK_UNKNOWN) abgelehnt. Der Datenexport (Art. 15) nennt statt der URLs nur noch ihre Anzahl (urls_configured). Behoben: Das Löschen der Home-Assistant-Einstellungen, des globalen OpenWeatherMap-Keys und der Speicher-Einstellungen meldete Erfolg und ließ den gespeicherten Wert stehen. Upgrade-Hinweis: Nach der Migration findet ein älteres Image diese drei Einstellungen nicht mehr (HA-Token und Pl@ntNet-Key fallen auf die Umgebungsvariablen zurück, Apprise sendet nicht); bei einem Rollback trage sie neu ein. - BREAKING (API): Teure authentifizierte Routen haben jetzt ein Budget pro Konto (Issue #2109). Datei-Uploads (Anhänge, Pflanzen- und Aufgabenfotos, Schädlingsbilder, CSV-Import inklusive Bestätigung) erlauben
RATE_LIMIT_UPLOAD(Standard30/minute), Inferenz-Routen (CV-Diagnose, Schädlingserkennung, Pflanzenbestimmung, Referenzbeitrag)RATE_LIMIT_INFERENCE(20/minute) und die PDF-Drucke unter/printRATE_LIMIT_EXPORT(20/minute) — jede Route zählt für sich. Darüber antwortet die API mit429und dem HeaderRetry-After, den jetzt jede429-Antwort trägt. Die EinstellungRATE_LIMIT_GENERALentfällt: Sie wurde nie gelesen, obwohl die Referenz sie als Limit der allgemeinen Endpunkte beschrieb. Ist sie in deiner Umgebung gesetzt, kannst du sie löschen. - BREAKING (API): HEIC/HEIF-Fotos werden nicht mehr angenommen (Issue #2139). Die EXIF-Bereinigung kann sie nicht neu kodieren, deshalb wurden sie samt GPS-Position gespeichert. Ein Upload antwortet jetzt mit
415; die Kamera- und Bestimmungsdialoge der App wandeln Fotos schon vorher in JPEG um. SolangeSTORAGE_STRIP_EXIFaktiv ist, lehnt der Upload jeden Bildtyp ab, den die Bereinigung nicht entfernen kann — auch wenn einSTORAGE_ALLOWED_MIME_TYPES_<KATEGORIE>-Override ihn wieder zulässt. Bereits gespeicherte HEIC-Fotos bleiben unverändert und werden bei einer Löschung nach Art. 17 weiterhin alsskippedprotokolliert. - BREAKING (API): Bilder über 40 Megapixel werden mit
413(IMAGE_PIXEL_LIMIT_EXCEEDED) abgelehnt, bevor sie dekodiert werden (Issue #2108). Bisher passierte eine 445 KB kleine PNG mit 12 000 × 12 000 Pixeln den Upload und die EXIF-Bereinigung legte pro Pixel ein Python-Objekt an (gemessen 80 Byte pro Pixel, rund 11,5 GB). Die Grenze gilt für alle Bild-Dekodierungen des Backends; Bestimmung und CV-Diagnose antworten bei einer Pixelbombe jetzt mit diesem Code stattPAYLOAD_TOO_LARGE. Palettenbilder (PNG imP-Modus) behalten ihre Farben, sie kamen bisher schwarz zurück. - Geändert (API): Ein fehlendes Vorschaubild stößt seine Erzeugung höchstens einmal in fünf Minuten an statt bei jedem Abruf (Issue #2108). Schlägt die Erzeugung endgültig fehl (zu groß, nicht dekodierbar oder nach drei Versuchen), antwortet
GET …/attachments/{id}/thumbnails/{size}mit404statt dauerhaft202; das MCP-Werkzeug für Tagebuchfotos meldet das Foto dann alsunavailable. - Sicherheit: Das Sicherheits-Audit
security_audit_loghält jetzt auch fest, wenn ein Platform-Admin ein Konto deaktiviert, reaktiviert oder dessen E-Mail-Bestätigung setzt oder zurücknimmt (eine Zeile je tatsächlich geändertem Flag), wenn ein Platform-Admin einen Mandanten sperrt oder reaktiviert und wenn die Verwaltung eines Mandanten oder ein Platform-Admin dessen Löschung beauftragt oder abbricht (Issue #2111, MT-014, NFR-011 R-38, REQ-024 AK-60). Bisher blieb davon keine Zeile; der Abbruch einer Löschung entfernte sogar den Löschnachweis. Die Zeilen nennen nur Konto- und Mandantenschlüssel; eine Kontozeile hat keinentenant_key, eine Mandantenzeile keinentarget_user_key— beide Felder sind inGET /api/v1/admin/platform/security-auditdeshalb jetzt nullbar. Eine Änderung von Name, Beschreibung oder Mitgliederlimit eines Mandanten schreibt keine Zeile. Keine Migration. - Sicherheit: Jede Änderung einer Mitgliedschaft (hinzufügen, Rolle oder Scopes ändern, entfernen, Austritt, Annahme einer Einladung, Anlegen eines Mandanten) schreibt jetzt eine Zeile in das persistente Sicherheits-Audit
security_audit_log(Issue #2111, MT-014, NFR-011 R-38, Migrationv0082): wer, wessen Mitgliedschaft, welcher Mandant, alte und neue Rolle bzw. Scopes, Anfrage-ID und Zeitpunkt — nur Konto- und Mandantenschlüssel, kein Freitext. Aufbewahrung 730 Tage (täglicher Tasksecurity_audit.purge_expired); bei einer Kontolöschung werden beide Kontoschlüssel zu Tombstone-Hashes. Platform-Admins lesen die Zeilen überGET /api/v1/admin/platform/security-audit. Der Service-Aufrufchange_member_scopesverlangt dafür den handelnden Account (actor_user_key). - Sicherheit (API):
POST /api/v1/admin/platform/tenants/{key}/membersundPOST /api/v1/admin/platform/users/{key}/membershipsverlangen jetzt den eigenen Step-up des Administrators (Issue #2106, MT-008, REQ-024 AK-59): der Körper trägtcurrent_password(oderstep_up_token/step_up_codefür die Aktionadmin_membership_add, gebunden an<tenant_key>|<user_key>); ohne ihn401, von einem API-Schlüssel403, nach Fehlversuchen429. Ein Plattform-Admin kann sich dem Mandantenplatformnicht selbst hinzufügen (422), und die Rolleleadim Mandantenplatformvergibt nur, wer sie hält. Zuvor genügte die Admin-Sitzung, um einen beliebigen Account — den Mandantenplatformmitleadeingeschlossen — ohne Spur hinzuzufügen. Die beiden Admin-Seiten (Mandant, Nutzer) fragen den Step-up beim Hinzufügen ab. - BREAKING (API): Eine E-Mail-Einladung nimmt nur noch das Konto an, das die eingeladene Adresse trägt und bestätigt hat (Issue #2115, MT-018, REQ-024 AK-61).
POST /api/v1/tenants/invitations/acceptantwortet mit403, wenn das annehmende Konto eine andere Adresse hat oder seine Adresse nicht bestätigt ist; die Einladung bleibt offen, es entsteht keine Mitgliedschaft. Zuvor genügte der Token: ein weitergeleiteter oder abgefangener Link verlieh jedem angemeldeten Konto die Mitgliedschaft, bis zur Rollelead. Einladungslinks bleiben unverändert für jedes angemeldete Konto gültig. Wer die Einladung mit einem anderen Konto annehmen will, lässt sie an die Adresse dieses Kontos neu ausstellen. - Sicherheit: Wer einen Tenant verlässt oder entfernt wird, bleibt dort nicht mehr Zuständiger der Aufgaben, und die Pflege-Erinnerungen gehen nur noch an aktive Mitglieder (Issue #2114, MT-017, REQ-024 AK-62). Zuvor behielten Aufgaben den Ex-Mitglied-Zuständigen, und
notifications.dispatch_due_carebenachrichtigte ihn weiter mit Pflanzennamen und Fälligkeiten. Die Tageszusammenfassung (notifications.send_daily_summary) fasst jetzt je (Nutzer, Tenant) zusammen — sie legte bisher alle Aufgaben eines Nutzers unter den Tenant seiner ersten Aufgabe. - Sicherheit (API): Ein deaktivierter Tenant sperrt seine Mitglieder jetzt wirklich aus (Issue #2105, MT-007, REQ-024 AK-48). Bisher verschwand er nach
PATCH /api/v1/admin/platform/tenants/{key}mitis_active=falsenur aus der Tenant-Liste; wer den Slug kannte, las und schrieb über/api/v1/t/{slug}/…und den HeaderX-Active-Tenantweiter (gemessen auf ArangoDB 3.12:200). Jetzt antworten beide mit demselben403wie für einen unbekannten Slug, ein Aufruf ohne Header fällt bei deaktiviertem persönlichem Tenant auf den globalen Katalog zurück. Über MCP war ein deaktivierter Tenant schon vorher nicht erreichbar (list_tenantsführt ihn nicht, ein Werkzeugaufruf antwortetnot_found); das belegt jetzt ein Test. Die Mitgliedschaften bleiben gespeichert: Reaktivieren stellt den Zugriff unverändert wieder her. Die Plattform-Administration unter/api/v1/admin/platform/…erreicht einen deaktivierten Tenant weiterhin. - Ein Mandant entsteht jetzt atomar mit seiner Gründer-Mitgliedschaft, beiden Kanten und der Zeile im Sicherheits-Audit — eine Transaktion statt vier einzelner Schreibvorgänge (Issue #2118, MT-022, REQ-024 AK-63). Zuvor ließ ein Fehler zwischen zwei Schreibvorgängen einen verwaisten Mandanten zurück (Kurzname belegt, niemand kann ihn löschen); eine Registrierung, deren Mandant-Schreibvorgang scheiterte, hinterließ ein Konto samt Anmeldeanbieter ohne persönlichen Mandanten, das die Wiederholung als „schon registriert" beantwortete. Schlägt jetzt ein Schritt der Registrierung fehl, wird das Konto zurückgenommen, und die Wiederholung ist eine echte Registrierung.
- Sicherheit (MCP):
set_nutrient_plan_phase_targetsmitdry_run=truelas die Phasen-Einträge des Nährstoffplans eines fremden Mandanten (Vorschaubefore, EC-Werte, Phasennamen), weilget_phase_entriesden Plan ohne Mandanten nachschlug (Issue #2104). Der Plan wird jetzt zuerst unter dem Mandanten des Aufrufers aufgelöst; ein fremder oder globaler Plan antwortet mitnot_found.get_phase_entriesverlangt dafürtenant_key. - BREAKING (API, MCP): Mandantenlose MCP-Schreib-Werkzeuge verlangen die Plattform-Admin-Rolle (Issue #2103, REQ-033 §4.4). Bisher genügte jedem
kp_-Key die Leitung im eigenen persönlichen Mandanten, sodass jeder Key-Inhaber überassign_species_phase_sequencedie Phasenfolge einer globalen Art umhängen konnte. Der Aufruf endet jetzt mitpermission.denied(Audit-ZeileDENIED),tools/listzeigt das Werkzeug nur noch Plattform-Admins, und ein auf einen Mandanten beschränkter Key ist nie Plattform-Admin. Das Werkzeug lehnt außerdem eine mandanteneigene Art mitnot_foundab. Betrifft nur Betreiber, die das Werkzeug mit einem Nicht-Admin-Key aufgerufen haben. - Sicherheit (API): Die Rückwärts-Abfragen
GET /api/v1/phase-sequences/{key}/speciesundGET /api/v1/phase-definitions/{key}/speciessowie das MCP-Werkzeuglist_species_by_phase_sequencenannten bisher Namen privater Arten aller Mandanten (Issue #2102). Sie liefern jetzt nur noch globale, eigene und freigegebene Arten; das Werkzeug nimmt dafür das optionale Argumenttenant(ohne: nur globale Arten). - BREAKING (API): Geteilte Workflow-Vorlagen (
tenant_key == "", etwa die generierten Aktivitätspläne einer Art) lassen sich nicht mehr an Ort und Stelle ändern oder löschen (Issue #2101, REQ-006 §4, REQ-049 §2.3).PUT /api/v1/t/{slug}/tasks/workflows/{key}legt dem Mandanten eine eigene Kopie an und ändert diese;DELETEauf die geteilte Vorlage sowie das Schreiben ihrer Phasen und Task-Templates über/tasks/phases/*und/tasks/templates/*antworten mit403. Dasselbe gilt für Task-Templates ohne Eigentümer, die Migrationv0035nicht zuordnen konnte. Wer sie bisher bearbeitet hat, dupliziert die Vorlage zuerst. - BREAKING (API): Globale (geteilte) Düngemittel sind nur noch vom Plattform-Administrator änderbar und löschbar (Issue #2100, REQ-049 §2.3).
PUT/DELETE /api/v1/t/{slug}/fertilizers/{key}auf ein Düngemittel mittenant_key == ""antworten für Mandanten-Rollen mit403, für ein Düngemittel eines anderen Mandanten mit404; das eigene Düngemittel bleibt für Gärtner änderbar, löschbar nur durch die Leitung. Wer bisher geteilte Düngemittel über einen Mandanten gepflegt hat, braucht dafür die Plattform-Rolle. Dieselbe Regel gilt seitdem aus einer Quelle für Substrate und Düngemittel. - Sicherheit: Der E-Mail-Kanal der Benachrichtigungen sendet nur noch an Adressen, deren Inhaber sie belegt hat (
email_confirmed_at, Issue #1948, REQ-023 v1.38, REQ-030 v1.7). Ein mitREQUIRE_EMAIL_VERIFICATION=falseregistriertes Konto bekommt keine Benachrichtigungs-Mail, bis es einen Bestätigungslink angeklickt hat — auch nicht nach dem Umstellen auftrue;POST /auth/resend-verificationstellt den Link auch solchen Konten aus. Bestehende verifizierte Konten behalten ihre Mail (Migration v0080 stempelt sie; Trockenlauf zählt sie). Beim Start warntemail_channel_without_verification, wenn ein Versender konfiguriert und die Bestätigung aus ist. - BREAKING (API): Die Konto-Löschung durch einen Plattform-Admin ist asynchron (Issue #1949, REQ-024 §1a.2, REQ-025 AK-IE-01).
DELETE /api/v1/admin/platform/users/{key}antwortet jetzt mit202 Acceptedund einem Body{erasure_key, status, requested_at, message}(zuvor204ohne Body). Die Anfrage prüft den Step-up, legt den Löschantrag an, deaktiviert das Konto, widerruft seine Sitzungen und benachrichtigt die anderen Mitglieder seiner persönlichen Gärten sofort (weiterhin ohne Karenzzeit); ein Celery-Task (retention.run_account_erasure) führt danach die Löschung aus — die persönlichen Mandanten in begrenzten Stapeln mit Lebenszeichen, dann den eigenen Plan des Kontos. Die Antworten500 ERASURE_INCOMPLETEund502dieses Endpunkts entfallen — ein fehlgeschlagener oder unvollständiger Lauf ist der Antragsstatuspartially_completed(der tägliche Lauf wiederholt ihn), lesbar über das neueGET /api/v1/admin/platform/erasures/{erasure_key}. Weiter synchron entschieden:401/403/404/422/429(Step-up, Berechtigung),409(ein Lauf hält den Antrag) und503(das Deployment kann nicht löschen; nichts geändert). Aufrufer, die auf eine fertige Löschung warteten, müssen den Status abfragen. Löschungen, die die alte synchrone Route vor dem Deploy gestartet hat, enden wie bisher; der tägliche Lauf setzt eine abgebrochene fort - Sicherheit (API):
PATCH /api/v1/admin/platform/users/{key}verlangt jetzt das eigene Step-up des Administrators, sobald esemail_verifiedoderis_activeändert — auch beim Senken (Issue #1992; zuvor nur beim Anheben). Bisher machte ein einzigesPATCH email_verified=false, auch über einen API-Schlüssel, ein etabliertes lokales Konto zum Kandidaten der Bereinigung unbestätigter Konten im nächsten Lauf, die es ohne Step-up und ohne Hinweis an die anderen Mitglieder seines persönlichen Gartens löschte. Ein gesenktesemail_verifiedwird jetzt vermerkt (email_verified_lowered_at), und die Bereinigung entfernt ein so vermerktes Konto nie; sie benachrichtigt außerdem die anderen Mitglieder eines persönlichen Gartens, bevor sie ihn löscht. Konten, die ein Administrator vor diesem Release herabgestuft hat, tragen keinen Vermerk; die Bereinigung verschont zusätzlich jedes Konto, das sich je angemeldet hat (last_login_at) — das deckt sie ab, es sei denn, sie haben sich nie angemeldet - BREAKING (API): Die Mandantenlöschung ist asynchron (Issue #1792, REQ-024 AK-53).
DELETE /api/v1/tenants/{slug}undDELETE /api/v1/admin/platform/tenants/{key}antworten jetzt mit202 Acceptedund einem Body{tenant_key, status, requested_at, message}(zuvor200mit{"message": "Tenant deleted"}bzw.204ohne Body). Die Anfrage prüft die Berechtigung und den Step-up, legt den Löschungs-Datensatz an und friert den Mandanten ein (Mitgliedschaften deaktiviert); ein Celery-Task (app.tasks.tenant_tasks.run_tenant_erasure) löscht danach in Stapeln zu je 1000 Datensätzen, jeder in einer eigenen ArangoDB-Transaktion, und meldet zwischen den Stapeln ein Lebenszeichen am Datensatz. Die Fehlerantworten500 TENANT_ERASURE_INCOMPLETEund502der Lösch-Endpunkte entfallen — Fehler des Laufs stehen am Datensatz und werden vom täglichen Lauf wiederholt; ab dem dritten erfolglosen Versuch erscheint das Log-Ereignistenant_erasure.escalated. Aufrufer, die auf einen abgeschlossenen Löschvorgang warteten, müssen den Mandanten als eingefroren behandeln. Beim Deploy bereits laufende Löschungen übernimmt der tägliche Lauf nach sechs Stunden ohne Lebenszeichen - Plant-Instance- und Pflanzdurchlauf-Pflanzen-Responses enthalten eingebettete
species- undcultivar-Kurzinfos (Denormalisierung), damit das Frontend lesbare Namen ohne zusätzliche Abfragen bilden kann - Ernte:
batch_idist in API-Responses nullbar (string | null) statt einer leeren Zeichenkette; der Eindeutigkeitsindex aufharvest_batches.batch_idistunique + sparse. Bestandsdaten werden von Migrationv0030angepasst - Mandanten: Pflanzen-ID (
instance_id) und Chargen-Kennung (batch_id) sind pro Mandant eindeutig, die Stellplatz-ID (slot_id) pro Standort — nicht mehr über alle Mandanten (Issue #2065, Migrationv0079). Bisher übersprang das Onboarding beim zweiten Mandanten, der dieselbe Art wählte, alle Pflanzen (onb-<Art>-<n>war vergeben), und eine Kennung, die ein anderer Mandant nutzte, wurde mit409abgelehnt — die Ablehnung verriet, dass ein anderer Mandant sie hält. Innerhalb eines Mandanten bzw. Standorts bleibt eine doppelte Kennung ein409 - Mandanten: Ein Pflanzdurchlauf folgt der gespeicherten Art eines Eintrags nicht mehr in die private Art eines anderen Mandanten (Issue #1963, Migration
v0081). Zeilen aus der Zeit vor den #1871-Korrekturen können nach Migrationv0067(die ihre Kante entfernt hat) noch eine solche Art nennen; das Starten eines solchen Durchlaufs antwortet jetzt mit404für die Art (den Eintrag auf eine lesbare Art ändern), Phasenverlauf und Bewässerungsbedarf überspringen sie. Migrationv0081leert außerdemequipment.location_keyund die fremden Schlüssel inwatering_logs.slot_keys, die auf den Standort oder Stellplatz eines anderen Mandanten zeigen; Standortzuweisungen und Durchlauf-Einträge werden nur gezählt (ihr Feld ist Pflicht) — die Zählungen vor dem Deploy prüfen - Mandanten: Eine Pflanze, die ein älterer Pflanzdurchlauf mit der privaten Art eines anderen Mandanten angelegt hat, zeigt den Namen dieser Art nicht mehr an. Pflanzenlisten, Pflanzendetail, Tagebuchübersicht, Druck-Etiketten und -Checklisten, Überwinterungs-Beschriftungen, Foto-Bewertung, Diagnose-Kontext, Gießvorschlag und das MCP-Pflanzendetail lösen die gespeicherte Art jetzt in deinem Mandanten auf: Eine Art, die du nicht lesen darfst, erscheint als unbekannt, nie als fremder Name (Issue #2082). Leser, die sie noch ungeprüft auflösen, stehen im Guard
test_stored_species_key_readers_are_decided. - Mandanten: Pflege-Dashboard, Winter-Erinnerungen und Pflegeprofil-Vorgaben einer Pflanze, die ein älterer Pflanzdurchlauf mit der privaten Art eines anderen Mandanten angelegt hat, nutzen diese Art nicht mehr: Das Dashboard zeigt für sie keinen Artnamen mehr (vorher den fremden), und Frostempfindlichkeit, Familie und Gießangaben der fremden Art steuern ihre Erinnerungen und Vorgaben nicht mehr (Issue #2082).
- Pflanzenschutz-Stammdaten: Die Schädlinge, Krankheiten und Behandlungen aus
plant_info.yamlundadventskalender.yamlsamt ihren Behandlungs-Verknüpfungen werden jetzt angelegt — die Lader lasen bisher Schlüssel, die die Dateien nicht verwenden (u. a. Bemisia tabaci, Septoria apiicola, Ferramol fehlten). Eine Zeile, die das Modell ablehnt, wird übersprungen und protokolliert (ipm_seed_row_invalid), statt Daten zu erfinden; derzeit „Alcohol Spray“ (chemisch ohne Wartezeit) - Betrieb (Rollback): Kein Image-Rollback unter die erste Migration eines zurückgezogenen Index (Issue #2064). Ein älteres Backend-Image legt beim Start die eindeutigen Indizes wieder an, die eine Migration entfernt hat, und die Migration läuft danach nie wieder — gemessen auf ArangoDB 3.12.8. Untergrenzen:
v0030(dichter Index aufharvest_batches.batch_id, enthalten ab v0.0.24),v0041(globaler Index aufspecies.scientific_name_normalized, ab v0.2.1),v0062(eindeutiger Index aufattachments.storage_key),v0064(auth_providersohne Konfiguration),v0069(fertilizersmandantenübergreifend),v0075(tanks.name),v0076(activities.name),v0077(workflow_templates.name),v0079(plant_instances.instance_id,harvest_batches.batch_id,slots.slot_id) — die letzten sieben sind in keinem Release enthalten, ein Rollback unter das nächste Release ist also ausgeschlossen. Jeder Start eines neuen Images entfernt einen so wieder angelegten Index erneut (Log-Ereignisretired_index_reappeared); fehlt sein Ersatz, bleibt er stehen,retired_index_refused_without_replacementerscheint mit Levelerrorund/api/v1/health/readymeldetretired_indexes_refusedgrößer 0 (der Status bleibtready). Nicht erneut entfernt wird der eindeutige Index aufattachments.storage_key. Ein alter Pod, der nach den neuen startet, legt den Index bis zum nächsten Start eines neuen Pods wieder an. Halten zwei Mandanten schon denselben Namen, startet das alte Image gar nicht (ERR 1210 unique constraint violated) - Pflegeerinnerungen: Das Abschließen einer fälligen Gieß-Aufgabe legt die Folgeaufgabe unmittelbar an — zuvor entstand sie erst beim nächtlichen Planungslauf
- Pflegeerinnerungen: Eine Bestätigung schließt nur noch Pflegeaufgaben, die heute oder früher fällig sind; eine bereits eingeplante Folgeaufgabe bleibt erhalten
- Mandanten (Härtung, Issue #2119): Jedes Repository, dessen Datensätze einem Garten gehören, entscheidet jetzt ausdrücklich über seine Mandantenbindung. Zwölf weitere — Anhänge, Pflanzentagebuch, Benachrichtigungen, Kalender-Feeds, Schädlingserkennungen, Schädlingsbilder, Pflanzenerkennungen, KI-Anbieter, Einladungen, Mitgliedschaften, Standort-Zuweisungen und Löschprotokolle — lehnen eine Listenabfrage ohne Garten ab, statt die Zeilen aller Gärten zu liefern. Arten, Substrate und Aktivitäten bleiben bewusst ausgenommen (globaler Katalog plus eigene Einträge) und nennen ihren Grund; der Guard
test_tenant_bearing_repositories_declare_their_scopehält das fest. Für dich ändert sich nichts. - Kalender-Feeds (Issue #2119): Bearbeitest du einen Kalender-Feed (Name, Filter, aktiv), bleibt er deinem Garten und deinem Konto zugeordnet — vorher verlor er beim Speichern seine Zuordnung, verschwand aus deiner Feed-Liste und ließ sich nicht mehr öffnen. Bearbeiten, Löschen und Token-Erneuern prüfen den Besitz jetzt selbst und antworten bei einem fremden Feed mit „nicht gefunden“.
- Datenschutz: Der persönliche Datenexport (Art. 15/20) enthält jetzt auch die Messwerte und Protokolle deines persönlichen Gartens (Issue #2165): Tanks mit Messungen, Befüllungen und Wartungen, Gieß- und Düngeprotokolle, die Sensoren und ihre Messwerte (einzelne Werte 90 Tage, Stundenmittel 2 Jahre, Tagesmittel 5 Jahre). Ein Abschnitt mit mehr als 100.000 Messwerten enthält die neuesten und nennt die Zahl der ausgelassenen. Messwerte, die die Home-Assistant-Anbindung vor dem 4. Oktober 2026 ohne Gartenbezug gespeichert hat, werden über die Sensoren deines Gartens zugeordnet. Das Export-Format steigt auf Version 1.2: Jeder Abschnitt nennt
records_omittedundomission_note. - Behoben (MCP, Issue #2169): Ein MCP-Idempotenz-Datensatz, dessen 24 Stunden abgelaufen sind, gilt beim Nachschlagen in der Datenbank nicht mehr als Treffer, auch wenn der stündliche Aufräumlauf ihn noch nicht gelöscht hat. Ein Datensatz mit fehlendem oder unlesbarem Ablaufzeitpunkt zählt als abgelaufen; bisher brach ein unlesbarer Ablaufzeitpunkt den Werkzeugaufruf mit einem Fehler ab.
- Behoben (KI-Glossar, Issue #2169): Nach jedem abgeschlossenen Neu-Einlesen der Wissensbasis (
ai.knowledge_service_ingest, sonntags 03:00 UTC) leert Kamerplanter den Glossar-Cache und erzeugt die Erklärungen aller kuratierten Begriffe neu (REQ-035 §4.3). Bisher stieß nichts diese Kette an: Die KI-Erklärungen liefen nach ihren 7 Tagen ab und wurden nie neu erzeugt, sodass jeder Begriff dauerhaft nur noch den redaktionellen Kurztext zeigte. Das Leeren erfasst jetzt auch den Redis-Cache, aus dem zuerst gelesen wird — sonst hätte die Neuerzeugung jede Variante für noch gültig gehalten. Kosten: ein LLM-Aufruf je Begriff, Sprache und Erfahrungsstufe pro Woche (derzeit 32 × 6 = 192); mit ausgeschalteten KI-Funktionen (ai_features_enabled) entfällt die Neuerzeugung.
Deployment (Helm)¶
- Behoben (Helm, Docker): Uploads über 1 MiB scheiterten am Frontend-nginx mit
413(Issue #2144, Audit-Lücke G-10). Weder die nginx-Konfiguration des Images noch die, die das Chart darüber legt, setzteclient_max_body_size; nginx' Standard von 1 MiB lehnte so jeden größeren Anhang, jedes größere Pflanzen- und Aufgabenfoto und jeden größeren CSV-Import ab, lange vor der Backend-Grenze von 25 MiB (gemessen imnginx-unprivileged-Image: 2 MiB und 25 MiB →413). Beide Konfigurationen nehmen jetzt Bodys bisstorage.maxFileSizeMb+ 1 MiB an (Standard26m); alles darüber — auch ein JSON-Body — lehnt nginx mit413ab. Das Chart setzt am Ingressmaindieselbe Grenze alsnginx.ingress.kubernetes.io/proxy-body-size(ingress-nginx schneidet sonst ebenfalls bei 1 MiB ab). Upgrade-Hinweis:ingress.mainhat jetzt Standardwerte im Chart; der Ingress entsteht weiterhin nur, wenn duhostsangibst, undenabled: falseschaltet ihn ab. Die frühere Doku-Empfehlungproxy-body-size: "10m"war zu klein für 25-MiB-Anhänge - Behoben (API): Eine Datei mit genau der erlaubten Größe wurde schon vor dem Lesen mit
413abgelehnt (Issue #2144). Die Vorprüfung überContent-Lengthverglich den ganzen Multipart-Body — Datei plus Trennzeilen und Formularfelder — mit der Datei-Grenze. Sie lässt jetzt 64 KiB für diese Hülle zu (eine gemeinsame Prüfung für Anhänge, Pflanzen- und Aufgabenfotos, Schädlingsbilder, Referenzbilder und CV-Diagnose); die Grenze für die Datei selbst bleibt exakt, ein Byte mehr ergibt weiterhin413. - Doku / Dev-Chart: Die Ingress-Beispiele (Helm, ArgoCD, Kubernetes) und
values-dev.yamlleiteten/apidirekt auf den Backend-Service. Die NetworkPolicy des Backends lässt aber nur das Frontend zu (Issue #1159), und nur über das Frontend-nginx gelten Upload-Grenze undTRUSTED_PROXY_HOPS: "1". Alle Beispiele führen jetzt/einschließlich/apizum Frontend; im Dev-Release leitet Vite/apiweiter. - Behoben (Helm): Ein Rolling Update blieb auf einem Cluster mit mehreren Nodes hängen, wenn der neue Backend-Pod auf einem anderen Node landete als das
ReadWriteOnce-Anhänge-Volume (Multi-Attach error); ein Worker auf einem anderen Node als das Backend startete gar nicht (Issue #2153). Backend und Celery-Worker verlangen jetzt perpodAffinitydenselben Node, solange die Anhänge auf einemReadWriteOnce-Volume liegen; das Rolling Update bleibt ohne Unterbrechung. Mitstorage.backend: s3oderReadWriteManygibt es keine Einschränkung. - Behoben (Helm): Der Celery-Worker hat eine Startup- und eine Readiness-Probe (Issue #2154). Bisher galt er beim Containerstart als bereit, und ein Rollout ersetzte den alten Worker auch dann, wenn der neue sich nicht bei ArangoDB anmelden konnte und jede Aufgabe scheiterte. Jetzt prüft der Worker (
WORKER_READY_FILE, vom Chart gesetzt) seine Anmeldung vor der ersten Aufgabe, beendet sich nach 60 Sekunden ohne Erfolg und meldet sich erst bereit, wenn sein Consumer läuft; der Rollout behält bis dahin den alten Worker. Die Liveness-Probe fragt nur noch den eigenen Worker — bisher gelang sie, solange irgendein Worker im Cluster antwortete. - Geändert (Betrieb): Celery verteilt Aufgaben auf drei Warteschlangen (Issue #2128, MT-032):
critical(Aufbewahrung und Löschung, Datenschutz-Aufräumläufe, Benachrichtigungen, Frostwarnung, Aktoren),celery(Standard, Name unverändert) undbulk(Datensatz- und Bilderfassung, Anreicherung, Glossar-Vorwärmen, Storage-Migration). Jede Aufgabe hat ein Zeitlimit als Notbremse (30 Minuten weich, 35 hart; fortsetzbare oder bewusst gestartete Läufe drei Stunden), jeder Worker-Prozess reserviert nur noch eine Nachricht im Voraus, und das Valkey-visibility_timeoutliegt mit vier Stunden über dem längsten Limit, damit eine reservierte Nachricht nicht ein zweites Mal ausgeliefert wird. Der Chart startet den Worker mit-Q critical,celery,bulk. Upgrade-Hinweis: Wer die Worker-argsüberschreibt, übernimmt die Queue-Liste oder lässt-Qweg; eine Queue ohne lesenden Worker behält ihre Aufgaben stillschweigend. Reihenfolge beim Rollout: Worker zuerst, dann Beat und API — eine ältere API schickt alles weiter ancelery, das der neue Worker liest. Noch offen aus MT-032: der N+1-Zugriff incheck_auto_transitions(derzeit nicht eingeplant). - Hinzugefügt (Betrieb): Ein Worker pro Queue (Issue #2128, MT-032): Mit
celeryWorkers.dedicatedQueues: truestartet der Chartcelery-workernur noch fürcriticalund zwei Kopien davon —celery-worker-defaultfürcelery,celery-worker-bulkfürbulk— beim Rendern aus den zusammengeführten Worker-Values abgeleitet — Overlays aufcontrollers.celery-workerundnetworkpolicies.celery-worker(Image, Umgebung,securityContext, Egress) gelten für alle drei, und jede Policy, die den Worker zulässt, lässt auch die Kopien zu. Standard bleibt ein Worker für alle drei Queues; das Rendering ohne den Schalter ist unverändert. Mit dem Standard-VolumeReadWriteOncelaufen beide Kopien zusätzlich auf dem Node des Backends (je 512 Mi angefordert). Idempotente Aufräumläufe (Aufbewahrung, abgelaufene Tokens und Einladungen, Audit- und Cache-Einträge, abgelaufene Overrides) bestätigen ihre Nachricht jetzt erst nach dem Lauf (acks_late), damit ein verschwundener Worker-Pod den Lauf nicht verliert. Die Pflege-Benachrichtigungen (notifications.dispatch_due_care,notifications.send_daily_summary) lesen nicht mehr alle Aufgaben aller Mandanten in den Worker, sondern fragen je aktivem Mandanten nur dessen offene, fällige Pflegeerinnerungen per AQL ab — der Speicherbedarf des Workers hängt nicht mehr von der Gesamtzahl der Aufgaben ab; scheitert das Lesen eines Mandanten, wird er protokolliert und übersprungen, die übrigen erhalten ihre Erinnerungen. Doku: Helm — Ein Worker pro Queue. - Geändert (Betrieb): Die Beat-Aufgaben für Aktoren, Tanks und Pflanzen lesen nicht mehr die Daten aller Mandanten in den Worker (Issue #2128, MT-032).
evaluate_control_rules,sync_actuator_states,sync_tank_states_from_ha,check_tank_alertsundevaluate_quarter_climateholen zuerst die Mandanten, die sie bearbeiten dürfen, und lesen dann nur deren Zeilen — Mandant, entfernte Pflanzen und fehlende Home-Assistant-Entität sind Teil der AQL-Abfrage — seitenweise zu je 1000 Zeilen. Der Worker hält damit eine Seite statt der ganzen Installation, und die Zeilen gesperrter oder zur Löschung vorgemerkter Mandanten werden gar nicht erst gelesen. Dasselbe gilt für die derzeit nicht eingeplanten Aufgabencheck_auto_transitions,check_dormancy_triggersundupdate_vernalization_progress; die beiden letzten bearbeiten dadurch auch keine Pflanzen inaktiver Mandanten mehr. Ohne Mandant (tenant_keyleer): Ein Aktor ohne Mandant wird im Regelkreis jetzt wie das Gerät eines gesperrten Mandanten behandelt — eine auslösende Sicherheitsregel entscheidet, sonst wird ein Gerät, das im Notfall abgeschaltet wird (z. B. eine Heizung), einmal ausgeschaltet; Zeitpläne und Overrides wirken nicht mehr. Bisher lief er wie ein Gerät eines aktiven Mandanten mit. Jeder Lauf mit solchen Geräten meldetactuator_control_tenantless_devicesmit ihrer Anzahl. Ein Tank ohne Mandant wird weiter auf Alarme geprüft; Tank- und Aktor-Abgleich mit Home Assistant sowie die Pflanzen-Aufgaben lassen Zeilen ohne Mandant aus (Home Assistant las für sie ohnehin nichts). Ein Dokument, das die Validierung nicht besteht, wird übersprungen und mit seinem Schlüssel protokolliert (tenant_walk_skipped_invalid_document), statt den Lauf abzubrechen. Die Log-Zählertanks_of_closed_tenantsentfallen. - Upgrade-Hinweis (Helm): Der Block
storagein den Chart-Values wirkt jetzt (Issue #2124, NFR-013 v1.9). Bisher las das Chart ihn nicht —storage.backend: s3lieferte trotzdemlocal-fsmit PVC aus. Das Chart leitet daraus jetzt dieSTORAGE_*-Variablen von Backend und Celery-Worker, das PVCbackend-attachments(nur beilocal-fs) und dessen Mounts ab; die S3-Credentials kommen als genau zwei Schlüssel aus dem Secret instorage.s3.credentialsRef(Defaultstorage-s3-credentialsmitSTORAGE_S3_ACCESS_KEY_ID/STORAGE_S3_SECRET_ACCESS_KEY). Eigeneenv-Einträge in deinen Values überschreiben die abgeleiteten Werte weiterhin. Neu verweigert: Mehr als eine Replica vonbackendodercelery-workerauf dem Standard-VolumeReadWriteOncebrichthelm template/helm upgrade/den ArgoCD-Sync mit einer Meldung ab, weil ein zweiter Pod auf einem anderen Node nie startet (Multi-Attach error). Ausweg:storage.backend: s3(Pflicht für geteilten Betrieb),storage.localFs.pvc.accessMode: ReadWriteManymit RWX-StorageClass, oder auf einem Cluster mit genau einem Nodestorage.localFs.singleNode: true. Das Anhang-PVC trägt zusätzlichargocd.argoproj.io/sync-options: Prune=false,Delete=false, damit ArgoCD es nach dem Wechsel auf S3 nicht löscht — migriere die Dateien vorher mitpython -m scripts.storage.migrate --from local-fs --to s3 --checksum-verify - Neu (Helm): ArangoDB-Sicherung im Chart (Issue #2122, NFR-002 v2.2). Mit
backup.enabled: trueundbackup.s3.bucketlegt das Chart den CronJobkamerplanter-arangodb-backupan:arangodumpder Anwendungsdatenbank, Upload per rclone nachs3://<bucket>/<prefix>/<Zeitstempel>/,<prefix>/LATESTals ablesbarer Wiederherstellungspunkt, serverseitige Verschlüsselung und Aufbewahrung (retentionDays, der neueste Dump wird nie gelöscht). Default aus — bestehende Installationen ändern sich nicht. Die Wiederherstellung in einen leeren Namespace beschreibt die neue Seite „Backup & Wiederherstellung“; sie wurde mit einem echten Dump durchgespielt - Behoben (Helm): Der Backend-Container startet unter einem
startupProbe(Issue #2125, NFR-002 v2.3). Bisher beendete die Liveness-Probe ihn rund 35 s nach dem Start — vor dem ersten Lauschen, solange Migrationen und Seeds noch liefen (gemessen auf einer leeren Datenbank: rund 40 s bis zum Start); eine wartende Replica bekam die vorgesehenen bis zu 600 s Wartezeit auf den Migrations-Lock nie. Der neue Probe gibt dem Start bis zu 900 s; Liveness und Readiness setzen erst danach ein - Sicherheit / Upgrade-Hinweis (Helm): Der ArangoDB-Pod erhält nur noch
ARANGO_ROOT_PASSWORDstatt des ganzenkamerplanter-secrets(Issue #2126, NFR-002 v2.4) — bisher lagenJWT_SECRET_KEY,FERNET_KEY,INTERNAL_SERVICE_TOKENund alle Mail-/API-Schlüssel im Datenbank-Pod. Backend, Celery-Worker, Celery-Beat und das Backup verbinden sich nicht mehr alsroot, sondern mit dem Kontokamerplanter(database.arangodb.appUsername), das ein neuer Containerapp-userim ArangoDB-Pod bei jedem Start mitARANGODB_PASSWORDanlegt — Lese-/Schreibrecht nur auf der Anwendungsdatenbank, kein Zugriff auf_system. Das Backend kann dafür mit einem solchen Konto starten (bisher brach es mit401 No read accessab, weil es zuerst die Datenbankliste über_systemlas). Wer in seinen ValuesARANGODB_USERNAME: rootoder einenvFromam ArangoDB-Container setzt (frühere Doku-Beispiele), behält den Root-Zugang, bis er beides entfernt;database.arangodb.appUsername: rootstellt das alte Verhalten ausdrücklich her. Empfohlene Folgeschritte: eigenesARANGODB_PASSWORDund das Root-Passwort in ein eigenes Secret (database.arangodb.rootPasswordSecret) — siehe „Datenbank-Zugänge“ im Kubernetes-Deployment - Neu (Helm): Autoscaler, Disruption-Budget und
ExternalSecretals Opt-in (Issue #2127, NFR-002 v2.6).controllers.<name>.horizontalPodAutoscalerundcontrollers.<name>.podDisruptionBudgetschalten HPA und PDB pro Controller zu;externalSecrets.app.enabled: truelässt den External Secrets Operatorkamerplanter-secretsaus deinem Secret-Store schreiben. Ohne diese Einstellungen rendert das Chart genau wie bisher. Neu verweigert: ein Autoscaler für Backend oder Worker auf demReadWriteOnce-Anhänge-Volume (wie schon mehrerereplicas), mehr als ein Pod oder ein Autoscaler fürcelery-beat,arangodbundvectordb, und ein PDB, das keinen Pod verdrängen lässt (jedes Leeren eines Nodes bliebe hängen) — auch mitmaxUnavailable: 0oder"0%"und mit einem Prozentwert fürminAvailable, den Kubernetes auf alle Pods aufrundet. Die Helm-Doku empfiehlt außerdem eine Speichergrenze mitnoevictionund AOF für Valkey (nicht als Default) und hält fest, dass externe Datenbanken noch nicht unterstützt sind - Doku (Helm): Die Startdauer des Backends ist jetzt dokumentiert (Issue #2125): bis zu 900 s Start, bis zu 600 s Warten auf die Migration eines anderen Pods, 300 s bis ein Migrations-Lock als verwaist gilt — und wie du das Budget für eine sehr lange Migration erhöhst. Migrationen und Seeds laufen weiter beim Start des Backends, ein eigener Migrations-Job ist nicht nötig. Ein Test hält die Tabelle mit Chart und Code gleich, ebenso die Untergrenze für
failureThreshold(70: 600 s Wartezeit plus 100 s Aufschlag für den eigenen Start) - Behoben / Upgrade-Hinweis (Helm): Valkey behält seine Warteschlange über einen Neustart (Issue #2127, NFR-002 v2.8). Das Chart setzte
valkey.dataStorage.size, einen Schlüssel, den das Valkey-Chart nicht kennt — es legte kein Volume an,/datalag auf einem flüchtigenemptyDir, und jeder Neustart verlor die wartenden Aufgaben. Jetzt bekommt Valkey ein eigenes Volume (kamerplanter-valkey, 1 GiB, bleibt beihelm uninstallerhalten), die StrategieRecreate,maxmemory 256mbmitnoeviction(ein voller Speicher lehnt Schreibzugriffe ab, statt Aufgaben zu verdrängen), das Append-Only-Log und ein Speicherlimit von 640 MiB (die Render-Prüfung verlangt mindestens das Doppelte vonmaxmemory). Das Upgrade startet Valkey einmal neu; Aufgaben, die in dem Moment warten, gehen verloren wie bei jedem Neustart bisher — lass die Warteschlange vorher leerlaufen. Ohne Standard-StorageClass bleibt ValkeyPending; nenne dann eine invalkey.dataStorage.className. Für die lokale Entwicklung korrigiertskaffold.yamldenselben Schlüssel. - Neu (Helm): TimescaleDB als Controller im Chart, standardmäßig aus (Issue #2127, NFR-002 v2.8).
controllers.timescaledb.enabled: trueschaltet Datenbank, Service, NetworkPolicy undTIMESCALEDB_ENABLEDbei Backend, Worker und Beat gemeinsam ein. Das Passwort kommt aus dem SchlüsselTIMESCALEDB_PASSWORDinkamerplanter-secrets, nie aus den Values; die Datenbank läuft ohne Root-Rechte mit schreibgeschütztem Dateisystem. Der auskommentierte Beispielblock mitPOSTGRES_PASSWORD: changemeist entfernt. Init-Container von Frontend und Valkey haben jetzt ebenfalls Ressourcen-Limits. - Neu (Helm): Externe ArangoDB und externes Valkey (Issue #2127, NFR-002 v2.9).
database.arangodb.external.enabled: trueschaltet die mitgelieferte ArangoDB ab und verbindet Backend, Worker, Beat und Backup mithost,portundscheme(httpoderhttps);valkey.enabled: falsemitdatabase.valkey.external.urltut dasselbe für Valkey. Für beide rendert das Chart eine Egress-NetworkPolicy auf den angegebenencidr. Das Backend liest das Schema aus der neuen VariableARANGODB_SCHEME(Standardhttp, bisher fest eingebaut) und verweigert den Start bei einem anderen Wert oder einer URL inARANGODB_HOST. Ohne die Schalter rendert das Chart wie bisher, ergänzt nurARANGODB_SCHEME=http— das Upgrade startet Backend, Worker und Beat deshalb einmal neu - Neu (Helm, Betrieb): Worker-Autoscaling nach Queue-Länge als Opt-in (Issue #2128, #2127, NFR-002 v2.10). Das Backend liefert auf seinem Metrik-Port jetzt
celery_queue_length(Labelqueue:critical,celery,bulk) undcelery_broker_up— gelesen beim Scrape, höchstens alle 5 Sekunden; ist Valkey nicht erreichbar, fehlen die Warteschlangen-Werte, der Scrape schlägt nicht fehl.celeryWorkers.queueAutoscaling.enabled: truegibt jedem Worker einenHorizontalPodAutoscalerauf dieser Reihe als External-Metrik, ausgewählt nach den Queues, die der Worker liest. Voraussetzung:monitoring.enabled: trueund ein Prometheus Adapter im Cluster, den du selbst installierst — die Regel steht in der Helm-Doku. Widersprüchliche Einstellungen (ohne Monitoring, ohne Metrikname oder Ziel,minReplicasnicht untermaxReplicas, ein zusätzlicher eigener Autoscaler am selben Worker) lehnt das Chart beim Rendern ab; das gilt fürminReplicas/maxReplicasund External-Metriken jetzt auch bei jedem handgeschriebenen Autoscaler. Standard: aus, bestehende Installationen bleiben unverändert.
In Entwicklung¶
- REQ-025 (Datenschutz/DSGVO): DSGVO Art. 15–21 Betroffenenrechte — spezifiziert, nicht implementiert
- REQ-027 (Light-Modus): Anonymer Zugang für lokale Instanzen — spezifiziert, nicht implementiert
- OAuth/OIDC: Vollständige Implementierung (Engine aktuell Stub)
- TimescaleDB-Integration: Repository und Migrations vorhanden, Feature-Flag gesteuert