Betreiber können den zentralen Werkzeugkatalog jetzt über die UI
pflegen (GET /betreiber/werkzeuge, GET/POST .../neu, GET/POST
.../{id}, POST .../{id}/loeschen) statt nur per SQL. Bearbeitet
ausschließlich zentrale (account_id IS NULL) Einträge — ein
mandantenspezifischer Katalogeintrag bleibt über diese Seiten
unerreichbar (404), das ist weiterhin Sache des jeweiligen Mandanten.
Ohne befüllten Katalog konnte bisher keine Bewertung tatsächlich zu
"genehmigt" mit einem echten Werkzeug führen.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
640 lines
29 KiB
Markdown
640 lines
29 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ß.
|
|
|
|
Rechte werden **als Prüfung an jeder Aktion** durchgesetzt (Middleware
|
|
je Handler), nicht als grob unterschiedene Seitenbereiche.
|
|
|
|
**Ebene 4 — Nutzerverwaltung ist umgesetzt** (`internal/web/admin_handlers.go`,
|
|
Middleware `requireAdmin`): `GET /verwaltung/nutzer` listet alle Logins
|
|
des eigenen Mandanten, `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 und gibt es auf einem anderen Weg weiter (konsistent mit dem
|
|
Onboarding-Stand unten: Einladungslink/CSV-Import/SSO sind noch nicht
|
|
gebaut). Das schließt die Lücke, dass Ebene 3 (Fachebene) bisher nur
|
|
über einen manuellen SQL-Insert nutzbar war, weil die Firma-
|
|
Registrierung ausschließlich einen `admin`-Nutzer erzeugt. Abteilungen-
|
|
Verwaltung, Anmeldeverfahren, eigene Werkzeug-Freigaben/-Sperrungen und
|
|
Rechnungsdaten (Ebene 4 laut Tabelle oben) sind weiterhin nicht gebaut.
|
|
|
|
**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 erledigt, Schritt 6-7 geplant)
|
|
|
|
**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`). **Noch nicht gebaut (Schritt 7):**
|
|
die Benachrichtigung, wenn sich im Katalog eine Eigenschaft ändert, auf
|
|
der eine aktive Genehmigung beruht (Wiedervorlage).
|
|
|
|
**Noch nicht gebaut (Schritt 6):** ein `registereintrag` wird bislang
|
|
NICHT automatisch aus einer Genehmigung erzeugt, und es gibt keinen
|
|
PDF-/CSV-Export. Geplant: Zweck, Abteilung, Werkzeug, Datenklasse,
|
|
Einstufung, Auflagen, Verantwortliche/r, Datum, Gültigkeit.
|
|
|
|
---
|
|
|
|
## 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
|
|
(Sammellink, Selbstanmeldung, Abteilung beim ersten Antrag), (2) CSV-
|
|
Import, (3) SSO (Entra ID/Google Workspace), (4) Antrag ganz ohne
|
|
vorheriges Konto über Mandantenlink + E-Mail-Bestätigungscode (Konto
|
|
entsteht nebenbei). Keins davon ist gebaut — aktuell nur Firma-
|
|
Registrierung.
|
|
|
|
**Abrechnung:** nach vom Kunden angegebener Mitarbeiterzahl, **nicht**
|
|
nach angelegten Nutzern — sonst entsteht der Anreiz, Mitarbeitende
|
|
nicht anzulegen. Noch nicht gebaut (kein Abo-System).
|
|
|
|
---
|
|
|
|
## 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 # Design-System (enconf-Basis, ungenutzt seit Pivot)
|
|
├── 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` — **noch nicht gebaut**, geplant für Schritt 6.
|
|
|
|
**Append-only:** kein UPDATE auf `audit_log` (Trigger `forbid_update_delete`,
|
|
wiederverwendet aus dem alten Produkt), ebenso `bewertung` und
|
|
`entscheidung`. `registereintrag` wird bei seiner Einführung ebenfalls
|
|
append-only.
|
|
|
|
---
|
|
|
|
## 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
|
|
7. Wiedervorlage und Katalog-Benachrichtigung
|
|
|
|
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** — die
|
|
Spezifikation will, dass der erste Nutzer beide Rollen gleichzeitig
|
|
hat; `app_user.role` ist aktuell ein einzelner Wert. Muss geklärt
|
|
werden: zwei Rollen pro Nutzer zulassen (Datenmodell-Änderung) oder
|
|
zwei `app_user`-Zeilen für dieselbe Person?
|
|
- **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.
|