feat!: Produktwechsel zu KI-Antragsprüfung — Phase 1 (Datenmodell, Regelwerk, Katalog)

Deklarix war eine Pre-Publish-Kennzeichnungsprüfung für Werbe-Content
(UWG/MStV). Dieser Scope wird komplett verworfen und durch eine
KI-Antragsprüfung ersetzt: Mitarbeitende beschreiben ein KI-Vorhaben,
das System leitet Datenklasse und KI-VO-Einstufung ab, gleicht sie
gegen einen Werkzeugkatalog ab und erzeugt einen Entscheidungsvorschlag
mit Herleitung — ein Mensch entscheidet, das System bereitet nur vor.

BREAKING CHANGE: Migration 0008 droppt alle werberechtsspezifischen
Tabellen (submission, finding, extraction, evidence_package,
participant, platform_connection, asset). account/app_user/session/
audit_log bleiben (Mandantentrennung, Login, Protokollierung sind
produktunabhängig) — app_user.role wechselt von
creator/agentur/marke/kanzlei/admin zu den fünf neuen Rollen
mitarbeiter/verantwortlicher/pruefer/admin/betreiber (vier
Mandanten-Rollen + eine plattformweite, siehe CLAUDE.md).

Entfernt: internal/extract, internal/dossier, internal/evidence,
internal/socialconnect, alte rules/*.yaml (UWG-Regeln), testdata/golden
— alles ausschließlich für das alte Produkt.

Neu, Phase 1 der Baureihenfolge ("Datenmodell, Regelwerk als YAML,
Katalogstruktur"):
- Store: abteilung (Stammdaten), werkzeug + werkzeug_sperre (der
  eigentliche Wert des Produkts — zentral gepflegter Katalog mit
  mandantenspezifischen Ergänzungen/Sperrungen, Pflichtfelder
  letzte_pruefung/quelle für jede Zusicherung), antrag (Fragebogen-
  Grundgerüst, Antworten als JSONB für den adaptiven Fragebogen aus
  Phase 2).
- internal/rules komplett neu: lädt und validiert drei YAML-
  Regelwerke (Datenklasse-Ableitung, KI-VO-Einstufung, Anforderungs-
  profil) aus rules/*.yaml — noch ohne Auswertungslogik gegen echte
  Fragebogen-Antworten (das ist Phase 3, bewusst erst nach dem
  Fragebogen aus Phase 2, der die exakten Fakten-Feldnamen festlegt).
  Offene fachliche Annahmen (Rangfolge der Datenklassen, Fragebogen-
  Lücke für die "verboten"-Varianten) explizit in rules/OPEN.md
  dokumentiert statt geraten.
- Web-Layer auf Minimalgerüst reduziert, das kompiliert und die neue
  Rollenwelt trägt: Firma-Registrierung (Ebene 1, erster Nutzer wird
  admin), Login/Logout, Plattform-Bereich (Ebene 5, nur betreiber:
  Dashboard, Accounts-Übersicht, Audit-Log) — Fragebogen (Ebene 2) und
  Fachebene (Ebene 3) folgen in den nächsten Phasen.
- CLAUDE.md komplett neu geschrieben: Produktbeschreibung, Fünf-Ebenen-
  Rollenmodell, Fragebogen-Spezifikation, Ableitungstabellen,
  Werkzeugkatalog, Bewertungslogik (geplant), Onboarding, offene
  Punkte (u. a. Postgres-RLS-Frage aus der Frontend-Spezifikation
  noch nicht entschieden, "Admin und Verantwortlicher gleichzeitig"
  beim Onboarding noch nicht datenmodelliert).

Volle Testsuite inkl. echter Postgres-Tests grün. End-to-End gegen
einen laufenden Server verifiziert: Firma-Registrierung legt Account +
admin-Nutzer an, Betreiber-Login leitet zu /betreiber, mandanten-
übergreifende Accounts-Liste sichtbar für betreiber, 404 für
mitarbeiter auf /betreiber, 303 zu /login ohne Sitzung.
This commit is contained in:
noroot
2026-08-28 21:39:04 +02:00
parent 6fd7831784
commit b4d4ee8d3c
96 changed files with 2348 additions and 7612 deletions

597
CLAUDE.md
View File

@@ -1,102 +1,242 @@
# Deklarix
> Prüfung und Nachweis für Werbe-Content. Pre-Publish-Kennzeichnungsprüfung
> und revisionssichere Archivierung für Creator, Agenturen, Marken und
> Kanzleien. `deklarix.de` / `deklarix.com`
> 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.
---
## Was das Projekt ist
## Produktwechsel (2026-08-28)
Drei Funktionen, mehr nicht:
1. **Pre-Publish-Prüfung** — Caption, Standbild und Vertragslage rein,
Risikobewertung mit konkreten Korrekturvorschlägen raus.
2. **Beweissicherung** — jeder veröffentlichte Beitrag wird mit Hash und
Zeitstempel archiviert: Screenshot, Caption, sichtbare Kennzeichnung,
verknüpfter Vertrag, Briefing.
3. **Verantwortungsmatrix** — wer hat was vorgegeben, wer hat freigegeben,
wer schuldet welche Prüfung (Creator / Agentur / Marke).
**Wer nutzt es, wer zahlt:** Creator nutzen es täglich, zahlen wenig
(Reichweite/Referenz). Agenturen und Marken zahlen richtig — sie haften
über § 8 Abs. 2 UWG mit und brauchen Freigabe-Workflow plus
Nachweisarchiv. Kanzleien lizenzieren Deklarix als Whitelabel für ihre
Mandanten (zahlen für Software, nicht für Mandantenzugang); das
Kanzlei-Verzeichnis im Produkt bleibt kostenlos.
**Rollen von Anfang an.** Eine Submission gehört nicht einem Nutzer,
sondern hat Beteiligte mit Rollen (`creator`, `agentur`, `marke`,
`kanzlei`). Alle vier existieren im Datenmodell ab dem ersten Commit.
**Kein Demo-Shortcut.** Es wird direkt produktionsreif entwickelt —
keine reduzierte Zwischenstufe mit manuell eingetragenen Daten oder ohne
Login als Selbstzweck.
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.
---
## Kernprinzip: Fakten trennen von Urteil
## Grundregel
**Stufe 1 bestimmt Fakten (deterministisch, kein LLM). Das Regelwerk in
Go fällt das Urteil.**
**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.
- **Stufe 1 — Extraktion (`internal/extract`, regelbasiert):** Ob eine
Gegenleistung vorlag, kann aus dem Text nicht zuverlässig erraten
werden — ein verschwiegener bezahlter Post sieht textlich identisch
aus wie ein echter organischer Post. Deshalb gibt der/die Einreichende
die Gegenleistung selbst an (Formularfeld). Was sich objektiv aus der
Caption bestimmen lässt, wird per Zeichenketten-Logik erkannt: ob ein
Kennzeichnungswort vorkommt, welcher Wortlaut, und ob es vor der
plattformspezifischen "mehr anzeigen"-Kürzung steht. Kein externer
Dienst, keine API-Kosten, kein Sprachmodell — daher auch keine
Interpretation kreativer/impliziter Formulierungen; was nicht über ein
bekanntes Kennzeichnungswort läuft, wird nicht erkannt.
- **Stufe 2 — Bewertung (Go, deterministisch):** Regelwerk als
versionierte YAML-Dateien (`rules/`). Jedes Finding referenziert
Regel-ID und Version (z. B. `WK-004 v3`). Regeln sind
plattformunabhängig — UWG/MStV gelten überall gleich, nur
Darstellungsdetails unterscheiden sich; die gehören in
Regelbedingungen/Extraktion, nie in separate Regelsätze pro Plattform.
**Absolute Regel:** Fundstellen stehen ausschließlich in den kuratierten
YAML-Regeln, nie in Freitext oder Extraktionslogik erfunden. Eine
halluzinierte Fundstelle ist in diesem Markt ein Totalschaden.
**Historie:** Ursprünglich war Stufe 1 als Claude-API-Aufruf geplant
(Kennzeichnung *und* Gegenleistung vom Modell erraten). Das wurde
bewusst verworfen — der Nutzer will keine laufende Anthropic-Abhängigkeit
für Deklarix selbst (getrennt von Claude Code, mit dem an Deklarix
programmiert wird). Die Idee "LLM sieht Kontext-Hinweise wie
Rabattcodes/Affiliate-Links, die reines Keyword-Matching übersieht"
bleibt ein späterer Ausbaupunkt, kein aktueller Blocker.
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.
---
## Stack
## Fünf Ebenen, fünf Rollen
Bewusst langweilig, ein Binary, lokal lauffähig:
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.
| Bereich | Wahl |
|---|---|
| Sprache | Go 1.26, Standard-Library wo möglich |
| Web | `net/http` + `html/template` + htmx, kein Frontend-Build, kein React |
| Datenbank | PostgreSQL über `pgx`, Migrationen über `golang-migrate` |
| PDF | serverseitig erzeugt |
| Zeitstempel | RFC-3161 von einer öffentlichen TSA |
| Extraktion | regelbasiert, `internal/extract`, keine externe Abhängigkeit |
| Design | `design/enterprise.css` (Enterprise Light Theme, enconf-Pattern, Primärfarbe `#1677ff`) |
| 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 |
Kein Docker für die App selbst, kein React, keine Queue. Neue
Abhängigkeiten nur nach Rückfrage.
**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ß.
**Warum Postgres statt SQLite:** Das Produkt läuft von Anfang an für
hunderte gleichzeitige Nutzer über mehrere Mandanten (Creator, Agentur,
Marke, Kanzlei). Ein revisionssicheres Beweisarchiv braucht robuste
Concurrent-Writes sowie Point-in-Time-Recovery/Replikation — das ist mit
SQLites Single-Writer-Modell nicht sauber zu bauen. Gehostet wird
Managed Postgres in der EU (DSGVO).
Rechte werden **als Prüfung an jeder Aktion** durchgesetzt (Middleware
je Handler), nicht als grob unterschiedene Seitenbereiche.
**Pfad:** `/var/www/deklarix` | **Git:** `https://git.netcell-it.de/projekte/deklarix` | **Branch:** `main`
**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).
**Die Auswertung gegen echte Fragebogen-Antworten ist noch nicht
gebaut** — das ist Schritt 3 der Baureihenfolge (siehe unten), bewusst
erst, wenn der Fragebogen (Schritt 2) die exakten Fakten-Feldnamen
festlegt.
---
## 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 (geplant, Schritt 5-7)
Der/die Verantwortliche wählt: genehmigt / genehmigt mit Auflagen /
abgelehnt / Rückfrage. Begründungsfeld ist bei Abweichung vom Vorschlag
Pflicht. **Snapshot bei Entscheidung:** Regelwerk-Version, Katalog-
Version und der vollständige Werkzeugdatensatz werden eingefroren — ein
späterer Katalog-Wandel darf nicht rückwirkend verändern, worauf eine
Entscheidung beruhte.
Genehmigungen erhalten ein Ablaufdatum (Vorschlag: 12 Monate, bei
`hochrisiko` 6). Ändert sich im Katalog eine Eigenschaft, auf der eine
aktive Genehmigung beruht, wird der/die Verantwortliche benachrichtigt
(Wiedervorlage).
Jede Genehmigung erzeugt automatisch einen Registereintrag: Zweck,
Abteilung, Werkzeug, Datenklasse, Einstufung, Auflagen, Verantwortliche/
r, Datum, Gültigkeit. Export als PDF und CSV.
---
## 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).
---
@@ -106,20 +246,15 @@ Managed Postgres in der EU (DSGVO).
/var/www/deklarix/
├── cmd/
│ └── deklarix/
│ └── main.go # Entry Point, HTTP-Server
│ └── main.go # Entry Point: Migration, Regelwerk laden, HTTP-Server
├── internal/
│ ├── extract/ # regelbasierte Fakten-Erkennung (Keywords, Kürzungs-Position)
│ ├── rules/ # YAML-Loader, Auswertung, Versionierung
│ ├── evidence/ # Hashing, Zeitstempel, Append-only-Log
│ ├── dossier/ # PDF-Erzeugung
│ ├── socialconnect/ # OAuth-Flow Instagram/TikTok (Plattform-Verbindung)
│ ├── auth/ # Passwort-Hashing, Session-Token (produktunabhängig)
│ ├── rules/ # Lädt/validiert Datenklasse-/Einstufungs-/Anforderungs-YAML
│ ├── store/ # Postgres, Migrationen
│ └── web/ # Handler, Templates
├── rules/ # YAML-Regeln, versioniert im Git
├── testdata/
│ └── golden/ # Golden-Tests: Beispielbeiträge + erwartetes Ergebnis
├── rules/ # YAML-Regelwerk + OPEN.md (offene fachliche Fragen)
├── design/
│ └── enterprise.css # Design-System (enconf-Basis)
│ └── enterprise.css # Design-System (enconf-Basis, ungenutzt seit Pivot)
├── packaging/
│ └── DEBIAN/
│ └── control.tmpl # .deb Package-Control-Template
@@ -136,135 +271,58 @@ Managed Postgres in der EU (DSGVO).
## Datenmodell
Plus drei Tabellen für Auth/Mandantentrennung (`account`, `app_user`,
`session`), mehr braucht der MVP nicht:
- `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`, `entscheidung`, `registereintrag` — **noch nicht
gebaut**, geplant für Schritt 5/6 der Baureihenfolge.
- `account` — ein Mandant (Creator, Agentur, Marke oder Kanzlei als
eigene Organisation); jede Submission gehört genau einem Account.
`verified` markiert einen Kanzlei-Account als für das öffentliche,
kostenlose Kanzlei-Verzeichnis (`GET /kanzleien`) freigegeben — nur
vom Admin-Bereich aus setzbar, nie vom Mandanten selbst
- `app_user` — ein Login innerhalb eines Accounts (E-Mail, Passwort-
Hash, Rolle). Rolle ist eine von `creator`, `agentur`, `marke`,
`kanzlei` **oder `admin`**. `admin` ist Betreiber-Personal
(Netcell-IT), nicht an einen Mandanten-Geschäftszweck gebunden,
zuständig für den Admin-Bereich (`/admin/...`: Accounts-Übersicht,
Kanzlei-Verzeichnis-Freigabe, Audit-Log). Es gibt **keine**
Selbstregistrierung für `admin` über `POST /register` (das Formular
bietet die Rolle nicht an) — der erste Admin wird einmalig per SQL
angelegt:
```sql
INSERT INTO account (name) VALUES ('Deklarix Admin') RETURNING id;
INSERT INTO app_user (account_id, email, password_hash, role)
VALUES ('<account-id>', '<login>', '<bcrypt-hash>', 'admin');
```
(bcrypt-Hash z. B. über `internal/auth.HashPassword` in einem
Wegwerf-`cmd/`-Programm erzeugen, da `internal/` von außerhalb des
Moduls nicht importierbar ist)
- `session` — eine angemeldete Sitzung (Token, Ablaufzeit); bewusst
eine echte Tabelle statt zustandsloser signierter Tokens, damit
Logout eine Sitzung wirklich beendet
- `audit_log` — Protokoll der Admin-Aktionen (wer hat wann welchen
Account wie verändert); append-only aus demselben Grund wie
`finding`/`extraction`/`evidence_package`
- `submission` — ein eingereichter Beitrag, Status, Zeitpunkte
- `asset` — hochgeladenes Standbild, Pfad, SHA-256; append-only aus
demselben Grund wie `finding`/`extraction`/`evidence_package` — ein
Beweisstück wird nicht nachträglich ausgetauscht. `purpose` = `initial`
(das Beweisfoto beim Prüfen, optional) oder `insights` (siehe
„Insights-Erinnerung" unten; ein Beitrag kann mehrere `insights`-Assets
über die Zeit bekommen). `GetLatestAssetForSubmission` berücksichtigt
nur `initial`, damit ein späterer Insights-Upload nie den beim
Archivieren referenzierten Original-Screenshot verdrängt
- `extraction` — das JSON aus Stufe 1, Modellversion, Prompt-Version
- `finding` — Ergebnis pro Regel: Regel-ID, Regel-Version, Schwere,
Titel, Korrektur, Fundstellen (zum Zeitpunkt des Findings fixiert,
kein Verweis auf die aktuelle Regel-YAML — die kann sich inzwischen
geändert haben)
- `evidence_package` — Dossier, Hash, Zeitstempel-Token, erzeugt am
- `participant` — Beteiligter an einer Submission mit Rolle
(`creator`, `agentur`, `marke`, `kanzlei`) und Beitrag zur
Verantwortungsmatrix (wer hat vorgegeben, wer freigegeben)
- `platform_connection` — die per OAuth hergestellte Verbindung eines
Accounts zu seinem eigenen Instagram- oder TikTok-Account (siehe
Abschnitt „Plattform-Verbindung (OAuth)" unten). NICHT append-only —
Tokens laufen ab und werden erneuert, eine Verbindung kann getrennt
und neu hergestellt werden; höchstens eine Verbindung pro
Account+Plattform (`UNIQUE(account_id, platform)`)
**Append-only:** kein UPDATE auf `audit_log` (Trigger `forbid_update_delete`,
wiederverwendet aus dem alten Produkt). `bewertung`/`entscheidung`/
`registereintrag` werden bei ihrer Einführung ebenfalls append-only.
**Append-only.** Kein UPDATE auf `finding`, `extraction`, `asset`,
`evidence_package` oder `audit_log`. Korrekturen sind neue Zeilen mit
Verweis auf die alte. Ein Beweisarchiv (bzw. Protokoll), in dem man
Zeilen ändern kann, ist keines mehr.
---
**Beweiskette:** SHA-256 über jedes Asset und über die kanonisierte
JSON-Repräsentation der Metadaten, RFC-3161-Zeitstempel über diesen Hash.
Dossier-PDF enthält Beitrag, Caption, Kennzeichnung, Findings mit
Fundstellen, Vertrags- und Briefing-Bezug, Verantwortungsmatrix, alle
Hashes und das Zeitstempel-Token. Vollständiger Export muss für den
Nutzer jederzeit möglich sein — wer kündigt, bekommt sein Archiv.
## Baureihenfolge
**Auth/Mandantentrennung:** `POST /register` legt einen neuen Account
plus den ersten Nutzer darin an, `POST /login` meldet einen bestehenden
Nutzer an — beides setzt ein `deklarix_session`-Cookie (HttpOnly,
SameSite=Strict, Secure sobald über TLS erreicht). `GET /` verlangt eine
gültige Sitzung (sonst Redirect zu `/login`); `POST /pruefen`,
`POST /veroeffentlichen` und `GET /dossier/{id}` verlangen sie ebenfalls
(sonst 401). Ein Beitrag eines fremden Accounts wird wie ein nicht
existierender behandelt (404), nie mit einer expliziten 403 bestätigt —
sonst würde die Antwort selbst verraten, dass die ID existiert.
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
3. Ableitungen und harte Filter (Auswertung gegen echte Antworten)
4. Ergebnisdarstellung mit Herleitung
5. Entscheidung, Snapshot, Audit-Log
6. Registereintrag und Export
7. Wiedervorlage und Katalog-Benachrichtigung
**Plattform-Verbindung (OAuth):** Jeder Kunde kann optional seinen
eigenen Instagram- oder TikTok-Account verbinden (`GET /verbindungen`),
damit die Beweissicherung einen veröffentlichten Beitrag künftig direkt
per API abrufen kann, statt ihn manuell hochzuladen — reiner
Authorization-Code-Flow, jeder Kunde autorisiert nur seinen eigenen
Account (`internal/socialconnect`, Persistenz in `platform_connection`).
Der manuelle Standbild-Upload bleibt der primäre Weg und funktioniert
unabhängig davon weiter; OAuth reduziert nur Reibung, ist kein
Ersatz für die Pre-Publish-Prüfung (die läuft zwingend vor
Veröffentlichung, wenn auf der Plattform noch nichts existiert — dafür
kann OAuth nichts abrufen).
Technisch ist der Flow fertig (Connector-Interface, CSRF-Schutz per
State-Cookie, Token-Speicherung), aber **ohne aktive Meta-/TikTok-
Freigabe nutzlos**: Instagram (`instagram_business_basic`) und TikTok
(Login Kit + Content Posting API) verlangen jeweils eine einmalige,
plattformseitige Prüfung des Deklarix-Betreiberkontos (Meta Business
Verification + App Review: ca. 24 Wochen; TikTok-Audit: ca. 12
Wochen), bevor sich beliebige Kunden selbst verbinden können. Bis dahin
lässt sich mit bis zu 25 (Meta) bzw. 10 (TikTok) manuell eingetragenen
Testern trotzdem schon mit einem echten Piloten testen. Ohne gesetzte
Konfiguration (`INSTAGRAM_CLIENT_ID`/`_SECRET`,
`TIKTOK_CLIENT_KEY`/`_SECRET`, `PUBLIC_BASE_URL`) zeigt
`GET /verbindungen` beide Plattformen als „noch nicht konfiguriert"
ohne Verbinden-Button — kein Absturz, kein stiller Fallback.
**Vorsicht bei künftigen Änderungen:** Instagram-/TikTok-Endpunkte,
Scopes und Token-Formate in `internal/socialconnect` wurden ohne echte
Zugangsdaten gegen die Entwicklerdokumentation gebaut, nie gegen die
echte API verifiziert — vor dem ersten echten Verbindungsversuch mit
realen Credentials die Konstanten in `internal/socialconnect/*.go` noch
einmal gegen die dann aktuelle Meta-/TikTok-Dokumentation prüfen.
**Insights-Erinnerung:** Story-Insights hält Instagram nach eigener
Aussage nur 24 Stunden vor — danach sind sie auch über den offiziellen
Datenexport nicht mehr zu bekommen, und der ursprüngliche Standbild-
Screenshot beim Prüfen (der direkt beim Veröffentlichen entsteht, bevor
nennenswerte Kennzahlen existieren) kann sie naturgemäß nicht erfassen.
`GET /beitraege/{id}` zeigt deshalb bei veröffentlichten `story`-
Beiträgen eine Erinnerung, solange keine `insights`-Asset existiert
(`internal/web/insights_reminder.go`, `computeInsightsReminder` —
reine Produktentscheidung zum Erinnerungs-Timing, keine Rechtsnorm,
daher bewusst nicht in `rules/*.yaml`). `POST /beitraege/{id}/insights`
speichert einen zusätzlichen Screenshot als `asset` mit
`purpose='insights'`, gehasht wie jedes andere Beweisstück — aber
NICHT im Metadaten-Hash des ursprünglichen Dossiers enthalten (das
wird beim Archivieren einmalig fixiert). Bewusst nur ein In-App-
Banner in dieser ersten Ausbaustufe, kein Mail-/Push-Versand — dafür
fehlt aktuell ein SMTP-Relay/Versanddienst; vor einer echten
Benachrichtigung per E-Mail ist das eine offene Rückfrage (welcher
Versanddienst, welche Absenderdomain/SPF/DKIM).
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.
---
@@ -385,56 +443,23 @@ 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.
**Golden-Test-Suite ist das eigentliche Asset des Projekts, nicht die
UI.** 30 bis 50 Beispielbeiträge in `testdata/golden/`, abgeleitet aus
veröffentlichten Urteilen und Beanstandungen der Medienanstalten, jeweils
mit erwarteten Findings. Jede Regeländerung läuft gegen die komplette
Suite. Eine Regeländerung, die ein Golden-Ergebnis kippt, wird nicht
gemerged, ohne dass der erwartete Wert bewusst und dokumentiert angepasst
wird. Extraktion wird separat getestet: fixierte Eingaben, geprüfte
JSON-Ausgabe.
---
## Leitplanken, die dauerhaft gelten
- **Keine Rechtsdienstleistung.** Deklarix ist ein Werkzeug, keine
Rechtsberatung. Keine Formulierung darf eine individuelle rechtliche
Bewertung suggerieren. Auf jedem Screen mit einem Ergebnis steht ein
entsprechender Hinweis. Keine Erfolgsaussichten-Aussagen.
- **Keine Versicherungssprache.** Nie „Abmahnschutz“, „abgesichert“,
„geschützt“, „Kostenübernahme“ — sonst erlaubnispflichtiges
Versicherungsgeschäft. Immer „Prüfung“, „Hinweis“, „Dokumentation“,
„Nachweis“.
- **Keine erfundenen Fundstellen.** Jede Norm und jedes Urteil steht in
einer Regel-YAML und wurde dort von Hand geprüft.
- **Nur eigene Inhalte.** Kein Scraping fremder Accounts, keine fremden
Posts als Demo-Material. Demo-Material ist synthetisch. Nutzerinhalte
kommen über eigenen OAuth-Zugang oder Datenexport.
- **DSGVO und Hosting in der EU.** Löschkonzept von Anfang an mitdenken,
aber Beweisarchiv und Löschpflicht sauber gegeneinander abgrenzen
(offener Punkt, siehe unten).
- **Berufsrecht der Anwälte (§ 49b Abs. 3 S. 1 BRAO).** Kein Routing
(kein Button, der einen Fall an eine Kanzlei übergibt — Nutzer wählt
selbst aus dem Verzeichnis), kein ergebnisabhängiger Preis, keine
Sachvorteile an gelistete Kanzleien, Verzeichnis bleibt kostenlos
(BGH IX ZR 89/23: entgeltliche Mandatsvermittlung ist nichtig). Vor dem
ersten Kanzlei-Vertrag: formlose Auskunft der zuständigen
Rechtsanwaltskammer einholen.
---
## Domains
| Domain | Verwendung |
|--------|-----------|
| deklarix.de | Primär (Marketing-Site) |
| app.deklarix.de | Die Anwendung selbst (dieser Go-Server) |
| deklarix.com | Redirect auf deklarix.de |
| get.deklarix.com | Installer-Endpoint (apt-Einrichtung, siehe Deployment) |
Kein separates `app.deklarix.com` — der Root-Redirect von `.com` auf
`.de` deckt Fehlnavigation ab, keine zweite Produktions-Subdomain nötig.
- **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.**
---
@@ -458,10 +483,7 @@ git push origin main
sudo systemctl start deklarix
sudo systemctl status deklarix
# Config: /etc/deklarix/deklarix.env (DATABASE_URL, PORT, RULES_DIR,
# DOSSIER_DIR, ASSET_DIR, TSA_URL, PUBLIC_BASE_URL,
# INSTAGRAM_CLIENT_ID/_SECRET, TIKTOK_CLIENT_KEY/_SECRET — letztere vier
# optional, ohne sie zeigt /verbindungen nur "nicht konfiguriert")
# Config: /etc/deklarix/deklarix.env (DATABASE_URL, PORT, RULES_DIR)
# Logs prüfen
journalctl -u deklarix -f
@@ -480,16 +502,11 @@ journalctl -u deklarix -f
## Arbeitsweise für Claude Code
- Kleine Commits, ein Thema pro Commit
- Erst Test oder Golden-Fall, dann Implementierung
- Erst Test, dann Implementierung
- Keine neue Abhängigkeit ohne Rückfrage
- Keine Umbenennung von Regel-IDs; Regeln werden versioniert, nicht
ersetzt
- Fehler nicht verschlucken, keine stillen Fallbacks bei der Extraktion
- Bei Unsicherheit über eine Rechtsfrage: nicht raten, sondern als offene
Frage in `rules/OPEN.md` notieren
- Der Code-RAG dient der Orientierung im Bestand, nicht als Nachweis von
Korrektheit. Verifiziert wird durch Compiler, Tests und die
Golden-Suite.
- Fehler nicht verschlucken, keine stillen Fallbacks
- Bei fachlicher Unsicherheit: nicht raten, sondern als offene Frage in
`rules/OPEN.md` notieren
## Vor Änderungen
@@ -508,13 +525,27 @@ journalctl -u deklarix -f
## Offene Punkte
- Umfang des Regelsatzes für den ersten Pilotkunden festlegen
- **TSA für die Zeitstempel:** interimsweise FreeTSA.org (frei, RFC-3161-
konform, siehe `internal/evidence.DefaultTSAURL`) — funktioniert
technisch, ist aber **nicht eIDAS-qualifiziert**. Vor echtem
Kundeneinsatz auf einen eIDAS-qualifizierten Zeitstempeldienst (z. B.
D-Trust, Bundesdruckerei) umstellen, der die gesetzliche
Vermutungswirkung nach eIDAS Art. 41 hat — dafür ist ein Vertrag/Account
nötig, kein anonymer HTTP-Call.
- Löschkonzept versus Beweisarchiv sauber ausformulieren
- Markenanmeldung Deklarix, sobald erster Kunde zahlt
- **Rangfolge der Datenklassen und Grenzfälle im Anforderungsprofil**
(siehe `rules/OPEN.md`) — vor Schritt 3 mit dem/der fachlich
Verantwortlichen (z. B. Datenschutzbeauftragte/r) bestätigen.
- **Genaue Fragebogen-Felder für die drei "verboten"-Varianten** (KI-VO
Art. 5) — Fragebogen-Spezifikation nennt nur eine C3-Frage ohne die
nötige Unterscheidung, siehe `rules/OPEN.md`.
- **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 ist leer** — "Werkzeugkatalog aus der
Plattform vorbelegt" beim Onboarding ist nicht automatisiert, es gibt
noch keine Ebene-5/Betreiber-UI zur Katalogpflege.
- Markenanmeldung Deklarix, sobald erster Kunde zahlt.