Files
deklarix/CLAUDE.md
noroot dbeaa45644 feat: Ampel-Kennzeichnung für Prüfalter im Werkzeugkatalog, Scroll-Fade für breite Tabellen
Katalogtabellen zeigten bisher nur "nicht aktuell geprüft" als
Binärflag; jetzt eine dreistufige Ampel (aktuell/bald fällig/
abgelaufen) mit denselben Farbtoken wie die KI-VO-Risikoampel.
Zusätzlich: .table-scroll bekommt einen rechten Fade-Rand, damit eine
am Fensterrand abgeschnittene breite Tabelle als "hier geht's mit
Scrollen weiter" erkennbar ist statt wie ein Rendering-Fehler
auszusehen.
2026-08-31 12:28:57 +02:00

43 KiB
Raw Permalink Blame History

Deklarix

KI-Antragsprüfung für Unternehmen: Mitarbeitende beschreiben ein KI-Vorhaben in eigenen Worten, das System leitet Datenklasse, KI-VO-Einstufung und Anforderungsprofil ab, gleicht sie gegen einen gepflegten Werkzeugkatalog ab und erzeugt einen begründeten Entscheidungsvorschlag. Ein Mensch entscheidet, das System bereitet nur vor.


Produktwechsel (2026-08-28)

Deklarix war ursprünglich eine Pre-Publish-Kennzeichnungsprüfung für Werbe-Content (UWG/MStV) für Creator, Agenturen, Marken und Kanzleien. Dieser Scope wurde komplett verworfen und durch das oben beschriebene Produkt ersetzt — kein Feature-Zusatz, ein Produktwechsel. Wiederverwendet wurden nur produktunabhängige Bausteine: Auth/Session/ Account-Mandantentrennung, das Prinzip "Regelwerk als versionierte YAML-Dateien, kein ML für die Bewertung", die apt/systemd/Gitea- Deployment-Pipeline und das Design-System. Alles Werberecht-Spezifische (Kennzeichnungsregeln, Beweisarchiv für Social-Media-Posts, Instagram/ TikTok-OAuth, Insights-Erinnerung) wurde entfernt, nicht nur ersetzt — siehe Migration 0008_pivot_ki_antragspruefung für den genauen Schnitt.


Grundregel

Das System entscheidet nicht. Es bereitet vor. Der/die Verantwortliche bestätigt, ändert oder überschreibt einen Bewertungsvorschlag — mit Begründungsfeld bei Abweichung. Jede Entscheidung wird als Registereintrag protokolliert. Die Bewertung wird nie ohne ihre vollständige Herleitung angezeigt (welche Antwort welche Anforderung ausgelöst hat) — ohne das ist ein Vorschlag im Audit wertlos.

Regelbasiert, nicht ML: die Ableitungsregeln (Datenklasse, KI-VO- Einstufung, Anforderungsprofil) liegen als versionierte YAML-Dateien vor, nicht im Code — ein fachlich Verantwortlicher soll sie anpassen können, ohne Go-Code anzufassen. Ein LLM ist v1 höchstens optional zur Zweckerkennung aus dem Freitext vorgesehen (Textgenerierung/Übersetzung/ Code/Bild/Transkription/Recherche erkennen), nie für die Einstufung selbst — und auch dort nur als Vorschlag, den der Mensch bestätigt.


Fünf Ebenen, fünf Rollen

Die Oberfläche gliedert sich in fünf Ebenen mit steigender Sensibilität. Vier Mandanten-Rollen sind eine Voreinstellung, kein fest verdrahtetes Modell (spätere Ausbaustufe: konfigurierbare Rollen) — betreiber ist technisch getrennt vom Mandantenbereich, keine Voreinstellung.

Ebene Zugang Rolle(n) Zweck
1 — Öffentlich ohne Konto Anmeldung, Passwort zurücksetzen, Einladung annehmen, Antrag über Mandantenlink
2 — Mitarbeiter Konto nötig mitarbeiter Antrag stellen, eigene Anträge verfolgen, Rückfragen beantworten, freigegebene Werkzeuge einsehen. Bewusst schlank — dauert der Antrag zu lange, wird er umgangen
3 — Fachebene Konto nötig verantwortlicher (Entscheidungsrecht), pruefer (identische Sicht, kein Entscheidungsrecht) Posteingang offener Anträge, Bewertungsvorschlag mit Herleitung, entscheiden, Register, Berichte/Exporte, Audit-Log lesend
4 — Mandanten-Verwaltung Konto nötig admin Nutzer/Rollen, Abteilungen, Anmeldeverfahren, eigene Werkzeug-Freigaben/-Sperrungen, Rechnungsdaten — bezogen auf GENAU EINEN Mandanten
5 — Plattform Konto nötig, technisch getrennt betreiber Werkzeugkatalog, Regelwerk-Versionierung, Mandantenverwaltung/Freischaltung, Nutzungssicht — Netcell-IT-Personal, plattformweit über alle Mandanten

Funktionstrennung bleibt bestehen, auch wenn in kleinen Firmen dieselbe Person Admin und Verantwortlicher ist — das sind zwei Rollen auf demselben Menschen, keine verschmolzene Rolle. admin (Ebene 4, Mandanten-Verwaltung für den eigenen Account) und betreiber (Ebene 5, Plattform-Betrieb für Netcell-IT über alle Mandanten) sind nicht dasselbe, auch wenn beide "Admin"-artige Rechte haben — nur betreiber ist der Nachfolger dessen, was früher (vor dem Produktwechsel) admin hieß. Praktische Umsetzung dieser Doppelrolle: admin bekommt zusätzlich dieselben Fachebene-Rechte wie verantwortlicher (siehe requireFachebene/hatEntscheidungsrecht) — pragmatisch gelöst ohne Datenmodell-Umbau, siehe Offene Punkte weiter unten.

Rechte werden als Prüfung an jeder Aktion durchgesetzt (Middleware je Handler), nicht als grob unterschiedene Seitenbereiche.

Ebene 4 ist inzwischen zu großen Teilen umgesetzt (Anspruch: alles, was im Datenmodell existiert, muss über das Frontend steuerbar sein, nicht nur einsehbar — Deklarix soll ein buchbarer Service werden, ein reiner Anzeige-Screen reicht nicht):

  • Nutzerverwaltung (internal/web/admin_handlers.go, Middleware requireAdmin): GET /verwaltung/nutzer listet alle Logins des eigenen Mandanten mit Status (aktiv/deaktiviert), GET/POST /verwaltung/nutzer/neu legt einen weiteren Login mit einer der vier Mandanten-Rollen an (mitarbeiter, verantwortlicher, pruefer, adminbetreiber kann kein Mandanten-Admin vergeben, das ist Ebene 5). Es gibt noch keine Einladungsmail — der Admin setzt das Initialpasswort direkt im Formular. Nutzer werden nicht gelöscht (app_user wird von antrag/entscheidung/audit_log per Foreign Key referenziert — ein Hard-Delete würde die Historie zerstören), sondern über POST /verwaltung/nutzer/{id}/deaktivieren bzw. .../aktivieren (de-)aktiviert (Spalte app_user.active, Migration 0012). Ein deaktivierter Login kann sich nicht mehr anmelden (handleLogin prüft Active erst NACH der Passwortprüfung, um keine Kontoexistenz zu verraten) und verliert eine bereits laufende Sitzung sofort (authenticate-Middleware prüft Active bei jedem Request). Ein Admin kann sich nicht selbst deaktivieren (Aussperr-Schutz).
  • Abteilungen (GET /verwaltung/abteilungen, anlegen + löschen): reine Stammdaten für das Fragebogen-Feld "Abteilung" — ohne diese Seite blieb die Abteilung-Auswahl im Fragebogen faktisch leer und unbenutzbar, das war kein Komfort-, sondern ein Funktionsdefizit.
  • Eigene Werkzeug-Sperrungen und -Einträge (internal/web/mandant_werkzeug_handlers.go, GET /verwaltung/werkzeuge): ein Mandant kann einen zentralen Katalogeintrag für sich sperren/entsperren (werkzeug_sperre), ohne den zentralen Katalog selbst zu verändern — das bleibt Sache der Plattform (Ebene 5, betreiber_werkzeug_handlers.go). Zusätzlich kann ein Mandant eigene, nur für sich sichtbare Katalogeinträge anlegen/ bearbeiten/löschen (GET/POST /verwaltung/werkzeuge/neu, GET/POST /verwaltung/werkzeuge/{id}, POST /verwaltung/werkzeuge/{id}/loeschenwerkzeug.account_id = der eigene Account). Nutzt dasselbe Formular wie der zentrale Katalog (werkzeugFormData/betreiber-werkzeug-form.html, ein ActionBase-Feld unterscheidet Betreiber- von Mandanten-Ziel-URL) — ein zentraler oder fremder Eintrag ist über diese Route nicht erreichbar (404).

Support-Login (Ebene 5, betreiber) ist umgesetzt — auf GET /betreiber/accounts/{id} kann sich der Betreiber über POST .../nutzer/{userID}/anmelden-als als ein aktiver Kunden-Nutzer anmelden, ohne dessen Passwort zu kennen (handleBetreiberLoginAls in betreiber_handlers.go, store.CreateImpersonatedSession, Migration 0014). Nicht für deaktivierte Nutzer möglich. Die neue Sitzung ersetzt die eigene Betreiber-Sitzung (kein Sitzungs-Stack/"Zurück zum Betreiber" — der Betreiber meldet sich danach mit den eigenen Zugangsdaten neu an). Jede Nutzung erzeugt einen audit_log-Eintrag (Action betreiber_login_als_nutzer); während der gesamten Sitzung zeigt die Nav einen auffälligen Banner ("Support-Zugriff durch ...", siehe currentImpersonator in middleware.go), damit nie unklar ist, im Kontext eines fremden Kontos zu handeln.

Firmen-CRUD für den Betreiber ist umgesetzt: GET/POST /betreiber/accounts/neu legt eine Firma samt erstem admin-Login an (dasselbe Ergebnis wie die öffentliche Registrierung, nur vom Betreiber ausgelöst — z. B. für vertriebsunterstütztes Onboarding oder Testkonten), POST /betreiber/accounts/{id}/umbenennen korrigiert den Firmennamen (store.UpdateAccount). Jede Firmenanlage erzeugt einen audit_log-Eintrag. Bewusst kein Löschen — ein Hard-Delete würde gegen die Fremdschlüssel aus antrag/app_user/audit_log laufen und Historie zerstören (dasselbe Muster wie bei Nutzern: deaktivieren statt löschen, aber ein Sperren/Deaktivieren-Zustand für Accounts existiert noch nicht, siehe unten).

Weiterhin nicht gebaut: Anmeldeverfahren-Konfiguration, das Abo-System. Wichtige Korrektur (2026-08-31): die eigentliche Zahlungsabwicklung/Rechnungsstellung läuft über ein anderes, externes System — Deklarix baut dafür kein Stripe/Zahlungs-SDK. Deklarix bildet nur den Subskriptionsstatus ab (Plan, gebuchte Mitarbeiterzahl, Status wie test/aktiv/gekündigt, Ablaufdatum), der von außen gesetzt wird — noch offen, wie genau ("von außen" heißt aktuell: manuell durch den Betreiber, eine Schnittstelle zum externen Abrechnungssystem ist nicht spezifiziert). Das Preismodell selbst (3 €/Mitarbeiter/Monat, Mindestabnahme 10 Mitarbeiter, 14 Tage Testphase) bleibt als Orientierung gültig, nur der Stripe-Teil der Empfehlung vom 2026-08-29 entfällt. Sperren/Deaktivieren eines Accounts durch den Betreiber hängt weiter an dieser (jetzt kleineren) Subskriptions-Architektur (siehe Offene Punkte: account.verified wurde beim Produktwechsel sogar entfernt).

Mandantenfähigkeit: jede Tabelle trägt account_id. Aktuell wird Isolation in der Anwendungsschicht erzwungen (Handler vergleichen AccountID gegen den angemeldeten Account, wie bisher) — die Zielvorgabe "Isolation auf Datenbankebene erzwungen" (Postgres Row- Level Security) ist eine bewusst noch nicht getroffene Entscheidung, siehe Offene Punkte unten.


Fragebogen (Ebene 2, "Antrag stellen")

Adaptiv: Folgefragen erscheinen nur, wenn die Antwort sie auslöst. Alltagssprache, keine Fachbegriffe — die stehen nur in der Ableitung.

A — Vorhaben: titel, beschreibung (Freitext: was soll die KI tun?), ergebnis (Freitext: was soll herauskommen?), haeufigkeit (einmalig/gelegentlich/täglich/automatisiert), abteilung (aus Stammdaten).

B — Daten (bestimmt die Datenklasse). Mehrfachauswahl Ja/Nein/ Unsicher, "Unsicher" zählt wie "Ja": Namen/E-Mail/Personenangaben (B1), Gesundheit/Religion/Herkunft/Gewerkschaft/Sexualleben (B2), Patienten-/ Mandanten-/Versichertendaten (B3), Bewerbungs-/Personal-/ Leistungsdaten Beschäftigter (B4), von Kunden anvertraute Daten (B5), Konstruktions-/Kalkulations-/Vertragsunterlagen (B6), nur allgemein zugängliche/erfundene Inhalte (B7).

C — Verwendung des Ergebnisses (bestimmt die KI-VO-Einstufung): geht das Ergebnis unverändert nach außen (C1)? beeinflusst es eine Entscheidung über einen Menschen (C2, Folgefrage: Einstellung/ Beförderung/Kündigung, Kreditwürdigkeit, Leistungsbewilligung, Zugang zu Bildung, medizinische Beurteilung, keins davon)? erkennt/bewertet es Emotionen, Verhalten oder biometrische Merkmale (C3)? läuft es ohne menschliche Prüfung (C4)? merkt der Empfänger, dass es von einer KI stammt (C5)?

D — Werkzeug: ggf. schon ein Werkzeug im Sinn (optional, Freitext), Firmen- oder privater Zugang, Firmen- oder Privatgerät.


Ableitungen (rules/*.yaml, internal/rules)

Datenklasse (rules/datenklasse.yaml) — höchste zutreffende Stufe gewinnt: oeffentlich (B7) < intern (B6) < auftragsdaten (B5) < personenbezogen (B1, B4) < berufsgeheimnis (B3, § 203 StGB) < besondere_kategorie (B2, Art. 9 DSGVO). Die Rangfolge ist eine Annahme (Spezifikation nennt keine expliziten Ränge) — siehe rules/OPEN.md, vor Produktiveinsatz mit dem/der Datenschutz- beauftragten bestätigen.

KI-VO-Einstufung (rules/kivo_einstufung.yaml) — Prüfreihenfolge, erste zutreffende Stufe gewinnt: verboten (Art. 5 KI-VO: C3 mit Emotionserkennung am Arbeitsplatz, Social Scoring oder biometrischer Kategorisierung) → hochrisiko (C2 mit Beschäftigung/Kreditwürdigkeit/ Leistungsbewilligung/Bildung/Gesundheit) → transparenzpflicht (C1 ja und C5 nein) → minimal (alles übrige). Die genauen Fragebogen-Felder für die drei "verboten"-Varianten sind noch nicht abschließend spezifiziert — siehe rules/OPEN.md.

Anforderungsprofil (rules/anforderungen.yaml) — aus Datenklasse und/oder Einstufung abgeleitete Menge von Anforderungen an ein Werkzeug: avv_erforderlich, eu_verarbeitung, kein_training_auf_eingabe, loeschfrist_max_tage, menschliche_aufsicht, kennzeichnungspflicht, dsfa_erforderlich.

internal/rules lädt und validiert diese drei Dateien (eindeutige IDs, eindeutige Ränge, jede Anforderung braucht mindestens einen Auslöser) und wertet sie seit Schritt 3 auch aus: EvaluateDatenklasse, EvaluateEinstufung, IstVerboten, DeriveAnforderungen operieren auf rules.Antworten (geparst aus antrag.antworten via ParseAntworten) — reine, für sich getestete Funktionen ohne DB-/Web-Zugriff. FilterWerkzeuge/ErfuelltAnforderung filtern einen Werkzeugkatalog hart gegen die abgeleiteten Anforderungen (WerkzeugEigenschaften ist ein schlanker, von store.Werkzeug unabhängiger Typ, damit internal/rules weiterhin ohne internal/store auskommt). Seit Schritt 4 wird das auch tatsächlich aufgerufen: POST /antraege berechnet nach dem Einreichen sofort eine bewertung (append-only, internal/web/antrag_handlers.go:bewerteAntrag) — bei verboten (K.-o.-Prüfung) ohne Werkzeugsuche — und GET /antraege/{id} zeigt sie inklusive Herleitung, RegelwerkVersion/KatalogVersion und dem Pflicht-Hinweis "Das System entscheidet nicht. Es bereitet vor."


Werkzeugkatalog (werkzeug, werkzeug_sperre)

Der eigentliche Wert des Produkts. Zentral gepflegt (Ebene 5, account_id IS NULL), für alle Mandanten identisch sichtbar; jeder Mandant kann zusätzlich eigene Einträge (account_id gesetzt) und Sperrungen zentraler Einträge (werkzeug_sperre) führen, ohne den zentralen Katalog zu verändern.

Pflicht: letzte_pruefung und quelle sind NOT NULL — jede Zusicherung im Katalog (AVV verfügbar, Verarbeitungsort, Training-Opt- out, Zertifizierungen) muss belegbar sein, sonst haftet Deklarix für eine Aussage, die nicht nachweisbar ist. Beide Katalogansichten zeigen eine dreistufige Ampel zum Prüfalter (pruefStatus() in betreiber_werkzeug_handlers.go, CSS-Klassen status-niedrig/ -mittel/-hoch, dieselben Farbtoken wie die KI-VO-Risikoampel): aktuell (grün, < 150 Tage), bald fällig (gelb, 150180 Tage — dasselbe 30-Tage-Vorwarnfenster wie bei der Wiedervorlage), abgelaufen (rot, > 180 Tage).

aufbewahrung_tage ist NULLABLE (Migration 0015) — NULL bedeutet "vom Anbieter nicht beziffert" und ist bewusst von 0 ("belegte Zusicherung: sofortige Löschung", z. B. DeepL) unterschieden. Vorher war die Spalte NOT NULL DEFAULT 0, wodurch beide Fälle identisch aussahen — formatAufbewahrung() zeigt entsprechend "unbekannt" statt "0 Tage".

geeignete_zwecke ist seit derselben Migration ein kontrolliertes Vokabular statt Freitext: das Formular zeigt Checkboxen für genau die sechs Zweck-Kategorien aus dem Fragebogen (Textgenerierung, Übersetzung, Code, Bild, Transkription, Recherche — gueltigeZwecke in betreiber_werkzeug_handlers.go), serverseitig validiert (istGueltigerZweck). Grund: ein künftiger automatischer Zweckabgleich (Bewertungslogik Schritt 3, weiterhin nicht gebaut) würde an inkonsistentem Freitext ("Text" statt "Textgenerierung") scheitern.

Neues Feld subprozessoren (TEXT[], Migration 0015) macht Unterauftragsverarbeiter (z. B. Anthropic bei Microsoft 365 Copilot, AWS bei DeepL) als eigenes, durchsuchbares Feld sichtbar statt nur als Freitext in einschraenkungen — das ist oft der Grund, warum ein scheinbar EU-verarbeitetes Werkzeug die USA doch nicht ausschließt.

Sowohl der zentrale Katalog (GET /betreiber/werkzeuge) als auch die mandantenseitige Sicht (GET /verwaltung/werkzeuge) zeigen den Katalog als Tabelle (Name, Anbieter, Verarbeitungsort, AVV, Training aus, Aufbewahrung, Zertifizierungen, Geeignete Zwecke, Zuletzt geprüft) statt als Liste — vorher waren nur Name/Anbieter/Ort auf einen Blick sichtbar, der Rest war erst nach Klick auf den Detaileintrag zu sehen.

Der zentrale Katalog ist aktuell mit 10 recherchierten Einträgen befüllt (ChatGPT Enterprise, Microsoft 365 Copilot, Claude for Work, GitHub Copilot Business, DeepL Pro/API, Google Gemini für Google Workspace, Le Chat Enterprise, Perplexity Enterprise Pro, Adobe Firefly Enterprise, Otter.ai Business) — deckt damit erstmals alle sechs Zweck-Kategorien ab (vorher fehlten Bild und Transkription vollständig, Recherche hatte kein dediziertes Werkzeug). Jede Zusicherung ist mit einer echten, offiziellen Anbieter-Quelle belegt (Stand 2026-08-31), nicht geraten — zwei Einträge (Adobe Firefly: Trainingsausschluss ist nicht Standard; Otter.ai: Training auf de-identifizierten Aufnahmen, laufende Rechtsstreitigkeiten) sind bewusst mit kritischen Einschränkungen versehen statt schöngefärbt, das ist die vorgesehene Funktion des Katalogs.

CurrentKatalogVersion liefert eine reproduzierbare Kennung des aktuellen Katalogzustands (Anzahl Einträge + letzte Änderung) — wird in jeder Bewertung/Entscheidung eingefroren, damit im Audit nachvollziehbar bleibt, mit welchem Katalogstand ein Vorschlag erzeugt wurde.


Bewertungslogik (geplant, Schritt 3+)

  1. K.-o.-Prüfung. Einstufung verboten → sofort Ablehnung (Art. 5 KI-VO), keine Werkzeugsuche.
  2. Harte Filter. Werkzeuge aussortieren, die eine Pflichtanforderung nicht erfüllen — Grund je Werkzeug festhalten, auch aussortierte Werkzeuge werden im Ergebnis mit Begründung gezeigt.
  3. Zweckabgleich. Aus beschreibung/ergebnis den Zweck ableiten (Textgenerierung, Übersetzung, Code, Bild, Transkription, Recherche) und gegen geeignete_zwecke filtern.
  4. Rangfolge. Verbleibende Werkzeuge sortieren: EU-Verarbeitung, Training standardmäßig aus, kurze Aufbewahrung, Aktualität der Prüfung.
  5. Auflagen ableiten. Statt Ablehnung Bedingungen erzeugen (Pseudonymisierung, keine Klarnamen, Vier-Augen-Prüfung, Kennzeichnung, Aufbewahrung begrenzen).
  6. Vorschlag erzeugen. Empfehlung, Alternativen, Auflagen, vollständige Herleitung.

Jede Bewertung wird als eigene Zeile gespeichert (append-only, wie finding/extraction im alten Produkt) — eine neue Bewertung ersetzt nicht die alte, sie ergänzt sie, damit Nachvollziehbarkeit auch bei mehrfacher Neubewertung eines Antrags erhalten bleibt.


Entscheidung, Register, Wiedervorlage (Schritt 5-7 erledigt)

Schritt 5 (Entscheidung, Snapshot, Audit-Log) ist umgesetzt. Ebene 3 (Fachebene) hat einen Posteingang (GET /faelle, alle offenen — Status "eingereicht" — Anträge des Mandanten, nicht nur die eigenen) und eine Entscheiden-Seite (GET /faelle/{id}, POST /faelle/{id}/entscheiden, siehe internal/web/fachebene_handlers.go). Rollen verantwortlicher und pruefer sehen dieselbe Sicht (requireFachebene), nur verantwortlicher hat Entscheidungsrecht — ein pruefer-POST auf /entscheiden bekommt 403 (er darf wissen, dass der Fall existiert, nur nicht entscheiden; anders als bei requireFachebene/requireBetreiber, die bei falscher Rolle 404 liefern, um die Existenz der Seite selbst zu verbergen).

Der/die Verantwortliche wählt: genehmigt / genehmigt mit Auflagen / abgelehnt / Rückfrage. Vorschlag-Vergleich: vorschlagFuer leitet aus der Bewertung ab, was das System vorschlagen würde (abgelehnt bei verboten, sonst genehmigt wenn mindestens ein zulässiges Werkzeug existiert, sonst rueckfrage) — weicht die tatsächliche Entscheidung davon ab, ist das Begründungsfeld Pflicht (sonst 400). Snapshot bei Entscheidung: entscheidung.werkzeug_snapshot (JSONB) friert den vollständigen store.Werkzeug-Datensatz zum Entscheidungszeitpunkt ein — ein späterer Katalog-Wandel darf nicht rückwirkend verändern, worauf eine Entscheidung beruhte. Bei "genehmigt"/"genehmigt mit Auflagen" ist ein Werkzeug aus der zulässigen Liste der Bewertung Pflicht (hart geprüft, kein Override eines ausgeschlossenen Werkzeugs). entscheidung ist append-only (Migration 0010) wie bewertung; jede Entscheidung erzeugt zusätzlich einen audit_log-Eintrag (Action antrag_entschieden).

Genehmigungen erhalten ein Ablaufdatum (gueltig_bis: 12 Monate, bei hochrisiko 6 — gueltigkeitFuer).

Schritt 7 (Wiedervorlage, Katalog-Benachrichtigung) ist umgesetzt (internal/web/wiedervorlage_handlers.go, GET /wiedervorlage, Ebene 3). Es gibt keine E-Mail-Infrastruktur — "Benachrichtigung" ist bewusst eine In-App-Liste, kein Versand. Eine aktive Genehmigung erscheint dort, wenn mindestens einer zutrifft: (1) gueltig_bis ist erreicht oder liegt innerhalb von 30 Tagen, (2) das zugesagte Werkzeug wurde aus dem Katalog entfernt, (3) werkzeugDiff erkennt eine Abweichung zwischen dem eingefrorenen werkzeug_snapshot und dem aktuellen Katalogeintrag bei AVV-Verfügbarkeit, Training-Standard oder Verarbeitungsort. Keine gefundene Abweichung → die Genehmigung erscheint nicht (kein stiller Blanko-Eintrag für jede Genehmigung). store.ListAktiveGenehmigungenForAccount liefert dafür alle genehmigt/genehmigt_mit_auflagen-Entscheidungen eines Mandanten (Join über antrag.account_id, da entscheidung selbst kein account_id trägt).

Schritt 6 (Registereintrag, Export) ist umgesetzt. Jede Genehmigung erzeugt automatisch einen registereintrag (append-only, Migration 0011): Zweck (antrag.titel), Abteilung, Werkzeug, Datenklasse, Einstufung, Auflagen (die Anforderungen der Bewertung), Verantwortliche/r, Entscheidungsdatum, Gültigkeit — siehe internal/web/register_handlers.go. GET /registereintraege (Ebene 3, verantwortlicher/pruefer) zeigt sie, GET /registereintraege/export.{csv,pdf} exportiert. Abgelehnte Entscheidungen und Rückfragen erzeugen bewusst KEINEN Registereintrag — das Register ist eine Genehmigungsliste, kein vollständiges Antragslog (das leistet bereits GET /faelle).


Onboarding

Firma (Ebene 1, öffentlich, POST /register): Registrierungsformular mit Firmendaten → Testzugang sofort aktiv → Freischaltung des Bezahlbetriebs nach Prüfung durch den Betreiber (letzteres noch nicht gebaut). Beim Anlegen entstehen automatisch: Account, erster Nutzer mit Rolle admin (siehe Rollentabelle — vereinfacht v1: nicht zusätzlich separat als verantwortlicher angelegt, siehe Offene Punkte), leere Abteilungsliste, Werkzeugkatalog aus der Plattform vorbelegt (noch nicht automatisiert — der zentrale Katalog ist aktuell leer, bis Ebene 5/Betreiber-Pflege gebaut ist).

Mitarbeiter, geplante Reihenfolge: (1) Einladungslinkerledigt (internal/web/einladung_handlers.go: account.einladung_token, Migration 0013, ein Sammellink pro Mandant statt Einmal-Token pro Person, kein Ablaufdatum, per Admin unter GET /verwaltung/einladung jederzeit erneuerbar [macht den alten Link sofort ungültig]. GET/POST /einladung/{token} — öffentlich, Ebene 1 — legt einen Login mit Rolle mitarbeiter im Mandanten des Links an und meldet direkt an, ohne E-Mail-Bestätigung [noch nicht gebaut, siehe unten]. Die Abteilung wählt die Person weiterhin erst beim ersten Antrag, nicht bei der Einladung.), (2) CSV-Importerledigt (internal/web/csv_import_handlers.go, GET /verwaltung/nutzer/import: CSV mit Kopfzeile, Pflichtspalte "email", optionale Spalte "role" [Standard mitarbeiter]. Wie beim einzelnen Nutzer-Anlegen gibt es keine Einladungsmail — pro Zeile wird ein Einmalpasswort erzeugt [wiederverwendet auth.NewSessionToken()], das NUR auf der Ergebnisseite direkt nach dem Import angezeigt wird und danach nirgendwo mehr abrufbar ist. Kein Alles-oder-nichts-Import: gültige Zeilen werden angelegt, eine ungültige Rolle oder bereits vergebene E-Mail in einer Zeile blockiert nicht die übrigen Zeilen — jede Zeile bekommt ihr eigenes Ergebnis [Passwort oder Fehlermeldung].), (3) SSO (Entra ID/Google Workspace), (4) Antrag ganz ohne vorheriges Konto über Mandantenlink + E-Mail-Bestätigungscode (Konto entsteht nebenbei). (3)-(4) sind weiterhin nicht gebaut.

Abrechnung: nach vom Kunden angegebener Mitarbeiterzahl, nicht nach angelegten Nutzern — sonst entsteht der Anreiz, Mitarbeitende nicht anzulegen. Noch nicht gebaut (kein Abo-System).


Frontend

Anspruch (2026-08-29 explizit vom Nutzer bestätigt, da Deklarix ein buchbarer Service werden soll): für Browser UND Mobile optimiert, und jede Entität im Datenmodell muss über das Frontend steuerbar sein — eine reine Anzeige-Seite ohne CRUD ist ein Defizit, kein v1-Kompromiss. Stack bleibt html/template + htmx, kein React/Vue/SPA (siehe Baureihenfolge/Architektur-Entscheidungen) — die responsive Optik wird rein mit CSS erreicht, ohne das Template-Modell zu ändern.

Navigation (internal/web/templates/layout.html, "nav"-Block, einmal definiert und von jeder Seite eingebunden — Änderungen hier wirken auf das ganze Produkt): ein Checkbox-Hack (versteckte Checkbox + <label for=...> als Klick-Ziel) klappt auf dem Handy ein Menü auf, ganz ohne JavaScript. Nicht das naheliegendere <details>/<summary> verwenden — aktuelle Chromium-Versionen steuern dessen Auf-/Zuklapp- Zustand über eine interne, mit CSS (auch nicht mit !important) nicht überschreibbare Animation; ein Versuch, .app-nav { display: flex !important } bei geschlossenem <details> zu erzwingen, bleibt wirkungslos (mit Chromium-Headless-Screenshot verifiziert). Ab 960px Breite (@media (min-width: 960px), app.css) wird exakt dieselbe Markup-Struktur per CSS zu einer permanenten, dunklen Seitenleiste (Farb-Tokens aus design/enterprise.css übernommen) — kein separates Desktop-Template.

Tabellen (table/th/td in app.css) sind seit dieser Änderung gestylt (vorher komplett ungestylt, betraf v. a. register_liste.html); breite Tabellen stehen in einem .table-scroll-Wrapper (horizontales Scrollen auf dem Handy statt gequetschter Spalten).

Verifikationsmethode für CSS-Änderungen: Live-Cookie-Auth per Chromium-Headless/CDP ist im Sandbox-Environment nicht möglich (kein websocket-Python-Modul) — stattdessen: Seite per curl -b cookies.txt authentifiziert abrufen, /static/...-Pfade im HTML auf eine absolute http://localhost:<port>/static/...-URL umschreiben (Assets brauchen kein Auth-Cookie), dann chromium --headless --disable-gpu --no-sandbox --window-size=<W>,<H> --screenshot=out.png file:///pfad/zur/seite.html bei mobiler (z. B. 390×844) und Desktop-Breite (z. B. 1440×900) screenshotten. Für einen Menü-Aufklapp-Zustand: checked-Attribut manuell ins gespeicherte HTML einfügen vor dem Screenshot.


Projektstruktur

/var/www/deklarix/
├── cmd/
│   └── deklarix/
│       └── main.go            # Entry Point: Migration, Regelwerk laden, HTTP-Server
├── internal/
│   ├── auth/                   # Passwort-Hashing, Session-Token (produktunabhängig)
│   ├── rules/                   # Lädt/validiert Datenklasse-/Einstufungs-/Anforderungs-YAML
│   ├── store/                   # Postgres, Migrationen
│   └── web/                     # Handler, Templates
├── rules/                      # YAML-Regelwerk + OPEN.md (offene fachliche Fragen)
├── design/
│   └── enterprise.css          # enconf Ant-Design-Theme — Referenz für Design-Tokens
│                                 (Farben/Radius/Schatten), nicht direkt eingebunden
│                                 (Ant-Design-Klassennamen passen nicht zu Deklarix'
│                                 reinem HTML/htmx-Stack, siehe internal/web/static/app.css)
├── packaging/
│   └── DEBIAN/
│       └── control.tmpl        # .deb Package-Control-Template
├── scripts/
│   ├── build.sh                # Cross-Compile amd64 + arm64
│   ├── test.sh                 # Tests + vet + build-check
│   └── release.sh              # Vollständiger Release-Prozess
├── go.mod
├── go.sum
└── CLAUDE.md

Datenmodell

  • account — ein Mandant (ein Unternehmen). Jeder Antrag gehört genau einem Account.
  • app_user — ein Login innerhalb eines Accounts. Rolle ist eine von mitarbeiter, verantwortlicher, pruefer, admin (alle vier Mandanten-bezogen) oder betreiber (plattformweit, Ebene 5).
  • session — eine angemeldete Sitzung (Token, Ablaufzeit); echte Tabelle statt zustandsloser signierter Tokens, damit Logout eine Sitzung wirklich beendet.
  • audit_log — Protokoll (append-only): jede Antragsänderung, jede Bewertung, jede Entscheidung, jede Katalogänderung mit Zeitstempel und Akteur.
  • abteilung — Stammdaten für Fragebogen-Feld A.abteilung, je Mandant.
  • werkzeug — Katalogeintrag (siehe oben); account_id NULL = zentral, gesetzt = mandantenspezifische Ergänzung. NICHT append-only — der Katalog wird gepflegt, eine Entscheidung friert den zu ihrem Zeitpunkt gültigen Datensatz separat ein (geplant).
  • werkzeug_sperre — Sperrung eines (auch zentralen) Katalogeintrags durch einen einzelnen Mandanten.
  • antrag — das Vorhaben aus Fragebogen-Abschnitt A plus die vollständigen Antworten aus B/C/D als JSON (antworten, JSONB) — ein starres Spaltenschema könnte den adaptiven Fragebogen nicht abbilden. NICHT append-only (normale Zustandsänderung entwurfeingereichtentschieden, wie submission es im alten Produkt war).
  • bewertung — der berechnete Vorschlag (Schritt 4).
  • entscheidung — die Entscheidung eines/einer Verantwortlichen über einen Antrag, mit Snapshot des gewählten Werkzeugs (werkzeug_snapshot, JSONB) und Ablaufdatum (gueltig_bis) bei Genehmigung (Schritt 5).
  • registereintrag — automatisch bei jeder Genehmigung erzeugt (Schritt 6), denormalisierter Nachweis (eigene Textspalten statt Joins über bewertung/entscheidung/werkzeug).

Append-only: kein UPDATE auf audit_log (Trigger forbid_update_delete, wiederverwendet aus dem alten Produkt), ebenso bewertung, entscheidung und registereintrag.


Baureihenfolge

  1. Datenmodell, Regelwerk als YAML, Katalogstrukturerledigt (dieser Stand: account/app_user/session/audit_log/ abteilung/werkzeug/werkzeug_sperre/antrag in Postgres, internal/rules lädt und validiert die drei YAML-Regelwerke, Web-Layer kompiliert mit Auth + Plattform-Bereich-Gerüst).
  2. Fragebogen mit adaptiver Logikerledigt (GET /antraege/neu einseitiges Formular, POST /antraege legt an und reicht direkt ein, GET /antraege eigene Anträge, GET /antraege/{id} Detail. Adaptive Folgefragen C2/C3 rein per CSS :has() ein-/ausgeblendet, kein JavaScript. antworten-JSON nutzt exakt die Fakten-Schlüssel aus rules/*.yaml (b1-b7, c1-c5, c2_folge, c3_art) — das hat auch die OPEN.md-Frage zu den "verboten"-Fragebogen-Feldern final geklärt.)
  3. Ableitungen und harte Filtererledigt (internal/rules: EvaluateDatenklasse/EvaluateEinstufung/DeriveAnforderungen/ IstVerboten/FilterWerkzeuge, vollständig getestet gegen die echten rules/*.yaml-Dateien.)
  4. Ergebnisdarstellung mit Herleitungerledigt (POST /antraege berechnet und speichert eine bewertung sofort beim Einreichen, GET /antraege/{id} zeigt Datenklasse, KI-VO-Einstufung, Anforderungen, zulässige/ausgeschlossene Werkzeuge — jeweils mit Herleitung — sowie RegelwerkVersion/KatalogVersion und den Pflicht-Hinweis "Das System entscheidet nicht. Es bereitet vor.". bewertung ist append-only. Getestet inkl. K.-o.-Prüfung (verboten überspringt die Werkzeugsuche) und hartem Filter gegen den Katalog.)
  5. Entscheidung, Snapshot, Audit-Logerledigt (Ebene 3: GET /faelle Posteingang, GET /faelle/{id} + POST /faelle/{id}/entscheiden. verantwortlicher entscheidet, pruefer sieht dieselbe Seite ohne Entscheidungsrecht [403 bei Entscheidungsversuch]. Begründung ist Pflicht bei Abweichung vom abgeleiteten Vorschlag (vorschlagFuer), Genehmigung friert den vollständigen Werkzeugdatensatz ein und braucht ein zulässiges Werkzeug aus der Bewertung, entscheidung ist append-only, jede Entscheidung erzeugt einen audit_log-Eintrag.)
  6. Registereintrag und Exporterledigt (internal/web/register_handlers.go: GET /registereintraege zeigt alle Registereinträge des Mandanten, GET /registereintraege/export.{csv,pdf} exportiert sie. Jede Genehmigung — genehmigt oder genehmigt_mit_auflagen — erzeugt in handleFallEntscheiden automatisch einen registereintrag [append-only, Migration 0011]: Zweck [antrag.titel], Abteilung, Werkzeug, Datenklasse, Einstufung, Auflagen [die Anforderungen der Bewertung], Verantwortliche/r, Entscheidungsdatum, Gültigkeit — bewusst denormalisiert [eigene Textspalten statt Joins], damit ein Eintrag nicht von einem späteren Katalog-/Abteilungs-Wandel abhängt. Abgelehnte Entscheidungen und Rückfragen erzeugen keinen Eintrag. Der PDF-Export nutzt das bereits vorhandene, bis dahin ungenutzte go-pdf/fpdf [keine neue Abhängigkeit] mit UnicodeTranslatorFromDescriptor für deutsche Umlaute.)
  7. Wiedervorlage und Katalog-Benachrichtigungerledigt (GET /wiedervorlage: abgelaufene/bald ablaufende Genehmigungen und Genehmigungen, deren Werkzeug sich seither im Katalog geändert hat oder entfernt wurde. In-App-Liste, kein E-Mail-Versand.)

Nicht bauen (v1): automatische Genehmigung ohne Mensch, Erkennung tatsächlicher Werkzeug-Nutzung, Mitarbeiterüberwachung (nichts, was Nutzung einzelner Personen auswertet), konfigurierbare Rollen mit eigener Oberfläche, Konzernstrukturen mit Vererbung, mandantenspezifische Regelwerke, Workflow-Designer, SAML, Schnittstellen zu Fremdsystemen.


Go Commands

export PATH=$PATH:/usr/local/go/bin   # Immer setzen!

# Entwickeln
go run ./cmd/deklarix/

# Tests
./scripts/test.sh
# oder direkt:
go test -race ./...
go vet ./...

# Build (amd64 + arm64)
./scripts/build.sh 1.0.0

# Build (nur amd64)
./scripts/build.sh 1.0.0 amd64

Build & Release-Prozess

Versioning (Semantic Versioning: MAJOR.MINOR.PATCH)

  • MAJOR — Breaking changes, API-Inkompatibilitäten
  • MINOR — Neue Features, rückwärtskompatibel
  • PATCH — Bugfixes

Release-Schritte

# 1. Alle Änderungen committen
git add -p && git commit -m "feat: ..."

# 2. Release-Skript (macht Tests → .deb-Build → Gitea-Upload → Tag → Push)
./scripts/release.sh 1.2.0

# Danach liegt in dist/:
#   deklarix_1.2.0_amd64.deb
#   deklarix_1.2.0_arm64.deb

Was das Release-Skript tut

  1. Prüft: sauberer Git-Status (keine uncommitted changes)
  2. Führt ./scripts/test.sh aus (vet + race tests + build-check, braucht DATABASE_URL)
  3. Baut .deb-Pakete für linux/amd64 und linux/arm64 (scripts/build.sh)
  4. Lädt die Pakete in Giteas Debian-Paketregistrierung hoch (Kanal testing, Suites bookworm + trixie) — siehe „Deployment“ unten
  5. Setzt Git-Tag v<version> mit Annotierung
  6. Pusht main + Tag nach origin

Deployment (apt, Infrastruktur-Standard wie bei enconf)

Updates laufen ausschließlich über ein apt-Paket, nie über manuelles Kopieren eines Binaries. .deb-Pakete landen in Giteas eingebauter Debian-Paketregistrierung unter der Gitea-Org projekte (gleicher Mechanismus wie beim Schwesterprojekt enconf-webpanel). Zielserver richten sich einmalig ein:

sudo mkdir -p /etc/apt/keyrings
sudo curl -fsSL https://git.netcell-it.de/api/packages/projekte/debian/repository.key \
    -o /etc/apt/keyrings/deklarix-gitea.asc
echo "deb [signed-by=/etc/apt/keyrings/deklarix-gitea.asc] https://git.netcell-it.de/api/packages/projekte/debian $(lsb_release -cs) testing" \
    | sudo tee /etc/apt/sources.list.d/deklarix.list
sudo apt-get update && sudo apt-get install deklarix

Danach: apt upgrade für Updates, systemctl {start,stop,status} deklarix für den Dienst. Konfiguration liegt in /etc/deklarix/deklarix.env (aus deklarix.env.example beim Erstinstall kopiert, DATABASE_URL ist dort absichtlich auskommentiert — der Dienst startet erst, wenn sie gesetzt ist; postinst startet ihn sonst bewusst nicht, um keine Crash-Restart-Schleife gegen einen Platzhalter-Host zu erzeugen). Aktuell gibt es nur den Kanal testing (kein stable — es gibt noch keine Kunden, für die eine Trennung nötig wäre).


Testing-Pattern & Qualitätssicherung

// Datei: internal/web/health_test.go
package web_test

import (
    "net/http"
    "net/http/httptest"
    "testing"
)

func TestHealth(t *testing.T) {
    req := httptest.NewRequest(http.MethodGet, "/health", nil)
    w := httptest.NewRecorder()
    HealthHandler(w, req)
    if w.Code != http.StatusOK {
        t.Fatalf("expected 200, got %d", w.Code)
    }
}
  • Tests liegen neben dem Code: paket/foo_test.go
  • Package: package foo_test (Black-Box-Test) oder package foo (White-Box)
  • Race-Detector immer an: go test -race ./...
  • Tabellenbasierte Tests für mehrere Inputs

DATABASE_URL und Testsystem. internal/store hat Integrationstests gegen echtes Postgres (u. a. die Append-only-Garantie). Ohne DATABASE_URL überspringt go test diese Tests einfach — praktisch für schnelle lokale Iteration ohne lokales Postgres. ./scripts/test.sh (und damit release.sh) ist strenger: es bricht ohne DATABASE_URL mit Fehler ab, statt die Append-only-Garantie still zu überspringen. Es zieht dafür aber kein eigenes Postgres hoch (kein Docker-Zwang auf dem lokalen Dev-Rechner) — das ist Aufgabe des dedizierten Testsystems, dort muss DATABASE_URL vorkonfiguriert sein.


Leitplanken, die dauerhaft gelten

  • Keine Rechtsdienstleistung. Deklarix bereitet eine Einstufung vor, entscheidet nicht. Keine Formulierung darf eine individuelle rechtliche Bewertung suggerieren oder Erfolgsaussichten behaupten.
  • Keine erfundenen Einstufungen. Jede Ableitungsregel steht in einer Regel-YAML und wurde dort von Hand geprüft; offene fachliche Fragen stehen in rules/OPEN.md, werden nicht geraten.
  • Werkzeugkatalog-Zusicherungen müssen belegbar sein. quelle und letzte_pruefung sind Pflichtfelder, keine Ausnahme.
  • Keine Mitarbeiterüberwachung. Nichts im Produkt darf auswerten, welche einzelne Person welches Werkzeug wie oft nutzt — das widerspricht dem Vertrauensmodell, auf dem die freiwillige Antragstellung beruht.
  • DSGVO und Hosting in der EU.

Wichtige Hinweise

Go PATH

# Immer setzen — ist nicht im Standard-PATH des Servers
export PATH=$PATH:/usr/local/go/bin

Git Push

git push origin main
# Remote: https://git.netcell-it.de/projekte/deklarix.git

Server-Prozess

# Dienst läuft über systemd (installiert per apt, siehe Deployment oben)
sudo systemctl start deklarix
sudo systemctl status deklarix

# Config: /etc/deklarix/deklarix.env (DATABASE_URL, PORT, RULES_DIR)

# Logs prüfen
journalctl -u deklarix -f

Bekannte Server

Rolle Host Zugang
Testsystem + app.deklarix.de (DB-Integrationstests, apt-Verifikation, nginx-Reverse-Proxy + Let's-Encrypt-TLS auf den lokalen Dienst) 89.163.205.4 SSH noroot, sudo passwortlos
Installer-Endpoint (get.deklarix.com) 89.163.205.110 noch nicht eingerichtet
Marketing-Site (deklarix.de/.com) 89.163.205.80 noch nicht eingerichtet

Arbeitsweise für Claude Code

  • Kleine Commits, ein Thema pro Commit
  • Erst Test, dann Implementierung
  • Keine neue Abhängigkeit ohne Rückfrage
  • Fehler nicht verschlucken, keine stillen Fallbacks
  • Bei fachlicher Unsicherheit: nicht raten, sondern als offene Frage in rules/OPEN.md notieren

Vor Änderungen

  1. go vet ./... — keine Fehler
  2. ./scripts/test.sh — alle Tests grün
  3. Bestehenden Code lesen — nicht raten

Nach Änderungen

  1. ./scripts/test.sh → 0 Fehler
  2. ./scripts/build.sh <version> → erfolgreich
  3. Commit mit semantischer Message: feat:, fix:, refactor:, docs:
  4. Bei Release: ./scripts/release.sh <version>

Offene Punkte

  • Rangfolge der Datenklassen (rules/OPEN.md, Punkt 1) — am 2026-08-29 vom Produktverantwortlichen bestätigt, keine Änderung nötig.
  • Anforderung kein_training_auf_eingabe und auftragsdaten (rules/OPEN.md, Punkt 2) — bestätigt, auftragsdaten bleibt eingeschlossen.
  • Genaue Fragebogen-Felder für die drei "verboten"-Variantenerledigt in Schritt 2, C3 hat jetzt eine Folgefrage c3_art. Ob die drei Varianten fachlich vollständig sind, wurde am 2026-08-29 ebenfalls bestätigt (siehe rules/OPEN.md, Punkt 3).
  • Fallback ohne zutreffende Datenklasse (rules/OPEN.md, Punkt 5) — bestätigt, intern bleibt die konservative Standardannahme.
  • Welche Anforderungen hart gegen ein Werkzeug gefiltert werden (rules/OPEN.md, Punkt 6 — avv_erforderlich/eu_verarbeitung/ kein_training_auf_eingabe hart, menschliche_aufsicht/ kennzeichnungspflicht/dsfa_erforderlich nur als Auflage vermerkt) — weiterhin nicht ausdrücklich bestätigt, aber plausibel, keine Rückmeldung dazu bisher eingeholt.
  • Löschfristen je Datenklasse (konkrete Tageswerte für loeschfrist_max_tage) — noch nicht fachlich festgelegt.
  • Mandantenisolation auf Datenbankebene (Postgres Row-Level Security) — die Frontend-Spezifikation fordert das explizit ("nicht in der Anwendungslogik"), aktuell ist Isolation ausschließlich anwendungsseitig (Handler-Vergleich AccountID). Umstellung auf RLS ist ein bewusst noch nicht getroffener Architektur-Entscheid — Aufwand und Zeitpunkt mit dem Nutzer klären, bevor mehr Tabellen entstehen, die sonst nachträglich migriert werden müssten.
  • "Admin und KI-Verantwortlicher" beim Firma-Onboardingpragmatisch gelöst, kein Datenmodell-Umbau: app_user.role bleibt ein einzelner Wert (kein roles-Array, keine zwei app_user-Zeilen pro Person). Stattdessen bekommt die Rolle admin in requireFachebene (Ebene 3) dieselben Rechte wie verantwortlicher — sieht Posteingang/Register/ Wiedervorlage und darf entscheiden (hatEntscheidungsrecht in fachebene_handlers.go). Grund: ohne das könnte eine frisch registrierte Firma mit nur einem admin-Login keinen einzigen eingereichten Antrag sehen oder bearbeiten — der Kernablauf wäre für Einzelpersonen-/Kleinfirmen-Onboarding komplett blockiert. pruefer bleibt unverändert nur lesend, ohne Entscheidungsrecht. Kompromiss statt "sauberer" Lösung — falls künftig eine Firma admin und verantwortlicher bewusst auf zwei verschiedene Personen verteilen will, funktioniert das weiterhin unverändert (zwei separate Logins mit den jeweiligen Rollen).
  • Zentraler Werkzeugkatalog muss noch befüllt werden — die Ebene-5/Betreiber-UI zur Katalogpflege ist jetzt gebaut (GET /betreiber/werkzeuge Liste, GET/POST /betreiber/werkzeuge/neu anlegen, GET/POST /betreiber/werkzeuge/{id} bearbeiten, POST .../loeschen löschen — siehe internal/web/betreiber_werkzeug_handlers.go. Bearbeitet ausschließlich account_id IS NULL-Einträge, ein mandantenspezifischer Eintrag ist über diese Seiten nicht erreichbar [404]). "Werkzeugkatalog aus der Plattform vorbelegt" beim Firma- Onboarding bleibt trotzdem nicht automatisiert — der Katalog ist inhaltlich weiterhin leer, bis der Betreiber tatsächlich Einträge pflegt.
  • Markenanmeldung Deklarix, sobald erster Kunde zahlt.