Löst einen Blocker im Kernablauf: eine frisch registrierte Firma hat nur einen admin-Login und konnte damit bislang keinen einzigen eingereichten Antrag sehen oder entscheiden, weil requireFachebene nur verantwortlicher/pruefer durchließ. Die Spezifikation will admin+ KI-Verantwortlicher ohnehin auf derselben Person (siehe CLAUDE.md, Offene Punkte) — statt eines Datenmodell-Umbaus (roles-Array oder zwei app_user-Zeilen pro Person) bekommt admin jetzt pragmatisch dieselben Fachebene-Rechte wie verantwortlicher (hatEntscheidungsrecht in fachebene_handlers.go). pruefer bleibt unverändert nur lesend. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
782 lines
38 KiB
Markdown
782 lines
38 KiB
Markdown
# 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`,
|
||
`admin` — `betreiber` 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}/loeschen`
|
||
— `werkzeug.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).
|
||
|
||
**Weiterhin nicht gebaut:** Anmeldeverfahren-Konfiguration, Rechnungsdaten/
|
||
Abrechnung (kein Abo-System, bewusst "Nicht bauen v1"), Account-
|
||
Verwaltung durch den Betreiber (Ebene 5 zeigt Accounts nur lesend an —
|
||
Bearbeiten/Sperren hängt an der noch nicht getroffenen Abrechnungs-/
|
||
Freischaltungs-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. Einträge älter als 180 Tage
|
||
sollen im Ergebnis als "nicht aktuell geprüft" markiert werden (noch
|
||
nicht in der UI umgesetzt, siehe Offene Punkte).
|
||
|
||
`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) ~~Einladungslink~~ —
|
||
**erledigt** (`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-Import~~ — **erledigt** (`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 `entwurf` →
|
||
`eingereicht` → `entschieden`, 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, Katalogstruktur~~ — **erledigt**
|
||
(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 Logik~~ — **erledigt** (`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 Filter~~ — **erledigt** (`internal/rules`:
|
||
`EvaluateDatenklasse`/`EvaluateEinstufung`/`DeriveAnforderungen`/
|
||
`IstVerboten`/`FilterWerkzeuge`, vollständig getestet gegen die
|
||
echten `rules/*.yaml`-Dateien.)
|
||
4. ~~Ergebnisdarstellung mit Herleitung~~ — **erledigt** (`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-Log~~ — **erledigt** (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 Export~~ — **erledigt** (`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-Benachrichtigung~~ — **erledigt**
|
||
(`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
|
||
|
||
```bash
|
||
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
|
||
```bash
|
||
# 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:
|
||
|
||
```bash
|
||
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
|
||
|
||
```go
|
||
// 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
|
||
```bash
|
||
# Immer setzen — ist nicht im Standard-PATH des Servers
|
||
export PATH=$PATH:/usr/local/go/bin
|
||
```
|
||
|
||
### Git Push
|
||
```bash
|
||
git push origin main
|
||
# Remote: https://git.netcell-it.de/projekte/deklarix.git
|
||
```
|
||
|
||
### Server-Prozess
|
||
```bash
|
||
# 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 und Grenzfälle im Anforderungsprofil**
|
||
(siehe `rules/OPEN.md`, Punkte 1/2/5/6) — mit dem/der fachlich
|
||
Verantwortlichen (z. B. Datenschutzbeauftragte/r) bestätigen. Schritt
|
||
3 ist trotzdem schon umgesetzt (auf Basis dieser dokumentierten
|
||
Annahmen) — nicht auf die Klärung gewartet, um nicht blockiert zu
|
||
bleiben, aber die Ableitung kann sich noch ändern.
|
||
- ~~Genaue Fragebogen-Felder für die drei "verboten"-Varianten~~ —
|
||
**erledigt in Schritt 2**, C3 hat jetzt eine Folgefrage `c3_art`.
|
||
Weiterhin offen: ob die drei Varianten fachlich vollständig sind
|
||
(siehe `rules/OPEN.md`, Punkt 3).
|
||
- **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-Onboarding~~ — **pragmatisch
|
||
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.
|