Commit Graph

73 Commits

Author SHA1 Message Date
noroot
7db4707bc5 feat: vollständige Firmendaten (Adresse, Abrechnung) bei Firmenanlage
Bei Firmenanlage (Registrierung + Betreiber-Firmenanlage) müssen jetzt
Adresse (Straße, PLZ, Ort, Land) und Abrechnungsdaten (Rechnungsemail,
optional USt-IdNr.) erfasst werden, nicht nur der Firmenname (Migration
0023). USt-IdNr. bewusst optional - Kleinunternehmer nach §19 UStG
haben keine. Neue Seite /verwaltung/firma (admin-only) zum Einsehen/
Nachtragen für bestehende Firmen. store.CreateAccount nimmt jetzt ein
AccountInput statt nur einen Namen entgegen (Signaturänderung betrifft
~20 Testaufrufe, mechanisch umgestellt). register.html/
betreiber_account_neu.html auf form-card/form-grid umgestellt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
v0.42.0
2026-09-01 11:36:05 +02:00
noroot
fa5e68c892 refactor: E-Mail-Vorlagen als Liste + eigene Bearbeiten-Seite statt Formular-Stapel
Auf Nutzerfeedback: die erste Version zeigte alle bekannten Vorlagen als
gestapelte Formulare auf einer Seite. Jetzt wie Werkzeugkatalog/
Nutzerverwaltung: eine Liste (GET .../email-vorlagen) mit Klick auf eine
eigene Bearbeiten-Seite je Typ (GET/POST .../email-vorlagen/{typ}) -
skaliert sauber, wenn mit der Zeit weitere Benachrichtigungstypen
dazukommen, statt eine immer länger werdende Formular-Stapel-Seite.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
v0.41.0
2026-09-01 11:10:46 +02:00
noroot
d3b171f721 feat: pro Mandant editierbare E-Mail-Vorlagen
Zweistufig wie der Werkzeugkatalog: Betreiber pflegt den plattformweiten
Standard (/betreiber/email-vorlagen), jeder Mandant kann ihn für sich
übersteuern (/verwaltung/email-vorlagen) - ResolveEmailVorlage nutzt die
eigene Vorlage falls vorhanden, sonst fällt sie auf den Plattform-
Standard zurück. Passwort-Zurücksetzen ist die einzige aktuell
existierende E-Mail und nutzt jetzt diese Vorlage statt Hardcoding.

Bug beim Live-Verifizieren gefunden: UNIQUE(account_id, typ) verhindert
bei NULLABLE account_id keine Duplikate (NULL != NULL in SQL) - jedes
Speichern des Plattform-Standards erzeugte eine neue Zeile statt sie zu
aktualisieren. Fix: zwei partielle Unique-Indizes statt eines
gemeinsamen Constraints, mit Regressionstest abgesichert.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
v0.40.0
2026-09-01 10:06:09 +02:00
noroot
b0d6b00045 fix: Migration 0021 crashte in Produktion — falsche Rollen-Annahme
v0.38.0 verursachte einen ~3-minütigen Ausfall: Migration 0021 nahm an,
die Anwendung verbinde überall als Postgres-Superuser "postgres" (nur
lokal via sudo geprüft, nicht die tatsächliche Produktions-DATABASE_URL)
und versuchte dort eine neue Rolle anzulegen - production verbindet
aber über die nicht-privilegierte, tabellenbesitzende Rolle "deklarix"
ohne CREATEROLE, das INSERT/CREATE ROLE schlug fehl und der Dienst
crash-loopte im "dirty migration"-Zustand. Kein Datenverlust (Postgres
hat die DDL-Transaktion selbst zurückgerollt), Dienst wurde auf v0.37.0
zurückgestuft und stabilisiert.

Fix: Rollen-Anlage ist jetzt an eine CREATEROLE-Prüfung gekoppelt und
wird bei fehlender Berechtigung übersprungen statt zu scheitern.
Zusätzlich FORCE ROW LEVEL SECURITY auf jeder Tabelle - das bindet auch
den Tabellenbesitzer (wie Productions "deklarix"), ganz ohne die
zusätzliche Rolle. Produktivbetrieb braucht dadurch jetzt gar keinen
manuellen Schritt mehr. Erneut end-to-end verifiziert, diesmal
zusätzlich produktionsgetreu simuliert (temporäre nicht-privilegierte,
tabellenbesitzende Rolle lokal).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
v0.39.0
2026-09-01 09:38:51 +02:00
noroot
fe28278615 feat: Row-Level-Security für Mandantenisolation auf DB-Ebene
Postgres-RLS-Policies auf allen Tabellen mit echten Mandanten-
Geschäftsdaten (antrag und alles darüber verkettete, abteilung,
werkzeug/werkzeug_sperre, genehmiger_rolle/freigabe_regel,
loeschfrist_einstellung). Kritischer Fund vor der Umsetzung: die
Anwendung verbindet als postgres-Superuser, der RLS immer umgeht -
Migration 0021 legt deshalb zusätzlich eine eingeschränkte Rolle
"deklarix_app" an, nur für die greifen die Policies tatsächlich.

internal/store/tenant_scope.go: WithTenantScope öffnet eine Transaktion
und setzt Sitzungsvariablen (app.account_id/app.is_betreiber) per
set_config mit Parameterbindung; alle Store-Methoden laufen jetzt über
s.db(ctx) statt direkt s.Pool. Jede require*-Middleware umschließt die
komplette Handler-Ausführung damit - jeder Request läuft dadurch auch
atomar in einer Transaktion (positiver Nebeneffekt).

Live end-to-end verifiziert (echter HTTP-Server + DATABASE_URL_APP auf
die eingeschränkte Rolle gesetzt): zwei Firmen registriert, Isolation
über Abteilung/Antrag/Bewertung bestätigt, zentraler NULL-Katalog für
beide sichtbar. Produktivbetrieb braucht noch einen manuellen Schritt
(Passwort für deklarix_app setzen + DATABASE_URL_APP konfigurieren,
siehe CLAUDE.md) - die Migration allein aktiviert noch nichts, solange
die App weiter als Superuser verbindet.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
v0.38.0
2026-09-01 09:22:20 +02:00
noroot
0fe29f8c80 fix: .page-wide auf alle Listen-/Dashboard-/Detailansichten ausgeweitet
Erste Version deckte nur die fünf Tabellen-Seiten ab. Auf Nutzerwunsch
jetzt auch Dashboard, Posteingang, Register, Nutzerverwaltung, Fall-/
Antrag-Detail, Wiedervorlage, Betreiber-Accounts/-Audit-Log/-Dashboard
und die neuen Genehmiger-Rollen/Freigaben-Seiten. Reine Formular-/Auth-
Seiten (Login, Registrieren, Einladung, Nutzer/Firma anlegen) bleiben
bewusst bei 1100px - ein einzelnes Formular auf voller Breite wäre
schlechter lesbar. .page-wide selbst wurde von "kein Limit" auf 1600px
korrigiert, da Detailseiten mit Fließtext (Fall-/Antrag-Detail) sonst
auf Ultrawide-Monitoren unlesbar lange Zeilen bekommen hätten.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
v0.37.0
2026-09-01 08:58:18 +02:00
noroot
eccf02050b fix: .page-wide hebt die 1100px-Breitendeckelung für Tabellen-Seiten auf
.page war für Formulare/Fließtext sinnvoll begrenzt, verschwendete auf
breiten Bildschirmen aber sichtbar Platz bei Tabellen mit vielen Spalten
(Werkzeugkatalog, Register, Freigabe-Regeln, Nutzer-Import-Ergebnis) -
die Tabelle musste horizontal scrollen, obwohl rechts noch Leerraum war.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-01 08:54:53 +02:00
noroot
8a8295dacd feat: Löschfristen je Datenklasse, Passwort-Zurücksetzen, DPF-Recherche
- Löschfristen (Migration 0020): pro Mandant einstellbar statt fest im
  Regelwerk, da die DSGVO selbst keine festen Fristen nennt (Art. 5
  Abs. 1 lit. e). Jede Firma wird mit risikogestaffelten Vorschlagswerten
  vorbelegt, loeschfrist_max_tage wird jetzt tatsächlich hart gegen
  Werkzeuge gefiltert (schließt rules/OPEN.md Punkt 4).
- Neues internal/mail-Paket (SMTP-Versand + Test-Doppel) und darauf
  aufbauend Passwort-Zurücksetzen (Migration 0019) - bisher nur als
  Absicht in der Rollentabelle genannt, nie gebaut.
- dpf_zertifiziert für alle 20 Katalogeinträge gegen das offizielle
  DPF-Register recherchiert und in der Produktions-DB aktualisiert
  (12 zertifiziert, 5 recherchiert-nicht-gefunden, 3 nicht anwendbar).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
v0.36.0
2026-09-01 07:38:12 +02:00
noroot
690660b655 feat: Standard-Genehmiger-Rollen automatisch bei Firmenanlage anlegen
Jede neue Firma (Registrierung + Betreiber-Firmenanlage) bekommt jetzt
automatisch vier leere Genehmiger-Rollen mit erklärender Beschreibung
(Datenschutzbeauftragter, Geschäftsführer, KI-Manager, CISO) - Admin
muss nur noch Personen zuordnen statt bei null anzufangen. Welche
Bedingung welche Rolle tatsächlich auslöst, bleibt weiterhin komplett
konfigurierbar pro Firma (Migration 0018 fügt genehmiger_rolle.beschreibung
als reines Freitext-Orientierungsfeld hinzu, keine feste fachliche
Bindung).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
v0.35.0
2026-08-31 22:18:49 +02:00
noroot
f729e5ae48 feat: konfigurierbaren Freigabe-Workflow mit Genehmiger-Rollen einführen
Mandanten können jetzt selbst festlegen, wer bei welcher abgeleiteten
Bedingung (Anforderung/Einstufung/Datenklasse) zusätzlich zur
Fachebene-Entscheidung zustimmen muss (z. B. Datenschutzbeauftragter,
Geschäftsführung) - Genehmiger-Rollen sind orthogonal zu den fünf
bestehenden Zugriffsrollen. Alle ausgelösten Rollen müssen zustimmen,
eine Ablehnung kippt kaskadierend den gesamten Antrag. Mandanten ohne
Freigabe-Regeln (aktuell alle) sind unverändert vom alten Direktpfad
betroffen, siehe TestOhneFreigabeRegelnVerhaeltSichWieVorher.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
v0.34.0
2026-08-31 21:42:28 +02:00
noroot
9e2c7f92ff feat: PageHeader/Filter/Paginierung auf Betreiber-Accounts, Audit-Log, Meine Anträge, Wiedervorlage ausgerollt
Vervollständigt den Rollout aus dem vorigen Schritt: Betreiber-Accounts
und Audit-Log (Betreiber-Bereich) sowie "Meine Anträge" bekommen
.page-header, Freitext-Filter (matchesQuery) und Paginierung.
Wiedervorlage nur .page-header, bewusst ohne Paginierung (natürlich
begrenzte Liste).

Audit-Log holt weiterhin nur die neuesten 1000 Einträge aus der DB
und paginiert im Go-Code darüber (auditLogFetchLimit) - keine echte
DB-Offset-Paginierung, als bekannte Einschränkung dokumentiert.
v0.33.0
2026-08-31 19:33:27 +02:00
noroot
4c60603055 feat: PageHeader/Filter/Paginierung auf Register, Nutzerverwaltung, Abteilungen, Posteingang, Betreiber-Dashboard ausgerollt
Register und Fälle-Posteingang bekommen .page-header, Freitext-Filter
und Paginierung; Register zusätzlich .table-responsive statt
.table-scroll. Nutzerverwaltung bekommt Filter+Paginierung (bleibt
Karten-Liste). Abteilungen nur .page-header (bewusst ohne Paginierung,
typischerweise kleine Listen). Betreiber-Dashboard von .admin-kacheln
auf .stat-cards umgestellt.

Neuer gemeinsamer Helfer matchesQuery() in pagination.go für die
Freitext-Filter aller Listen-Seiten.

Dabei einen bereits live gewesenen CSS-Bug gefunden und behoben:
.filter-bar direkt auf einem <form> verlor gegen "form {
flex-direction: column }" (gleiche Spezifität, nur form setzte die
Property), wodurch die Filterleiste senkrecht statt waagerecht
stapelte — betraf die im letzten Schritt ausgelieferte
Mandanten-Werkzeugkatalog-Seite. Jetzt robust via explizitem
flex-direction: row in .filter-bar selbst.
v0.32.0
2026-08-31 16:12:41 +02:00
noroot
bc99858c94 feat: enconf-Layout-Patterns übertragen — responsive Card-Tabellen, PageHeader, StatCards
Über die Architect-RAG-Suche (ac_search_code_global) drei UX-Muster
aus dem enconf-Schwesterprojekt (React/Ant Design) identifiziert und
als reines CSS/HTML nachgebaut, da Deklarix bewusst kein SPA ist:

- .table-responsive: Tabellen werden unter 640px zu einer Card-Liste
  statt horizontal zu scrollen (enconfs ProTable-Verhalten), via
  data-label-Attribut je <td>. Angewendet auf beide
  Werkzeugkatalog-Tabellen.
- .page-header: Titel+Unterzeile links, primäre Aktion rechts, statt
  eines nackten <h1> mit Link irgendwo unten.
- .stat-cards: Kennzahl-Kacheln mit farbigem Akzentrand (nur farbig
  bei Wert > 0) statt einfacher Text-Links, auf der Startseite.
v0.31.0
2026-08-31 15:55:34 +02:00
noroot
3aecbb446c feat: Filter und Paginierung für den Werkzeugkatalog, Claude Code als Katalogeintrag
Beide Katalog-Ansichten (Betreiber + Mandant) haben jetzt eine
Filterleiste (Freitext über Name/Anbieter/Verarbeitungsländer,
Zweck-Dropdown) und serverseitige Paginierung (20/Seite) über ein
GET-Formular, kein JavaScript. pagination.go ist generisch gehalten
(Go-Generics) für Wiederverwendung in weiteren Listen.

Claude Code als eigener Katalogeintrag ergänzt (nicht identisch mit
Claude for Work: läuft lokal, hat Dateizugriff, lokale Sitzungs-
protokolle mit eigenem Aufbewahrungsrisiko).
v0.30.0
2026-08-31 15:09:23 +02:00
noroot
08c7721165 docs: DPF-Recherche als offenen Punkt festhalten 2026-08-31 13:55:58 +02:00
noroot
59e1bcc8f7 feat: Werkzeugkatalog nennt tatsächliche Verarbeitungsländer statt EU/USA/gemischt-Eimer, plus DPF-Feld
Die USA sind DSGVO-rechtlich bereits ein Drittland wie jedes andere -
der alte verarbeitungsort-Wertebereich (EU/USA/gemischt/on-prem)
verschleierte das. Ersetzt durch verarbeitungslaender TEXT[] mit
echter Weltländerliste im Formular (Mehrfachauswahl, kein JS nötig).
Der harte Filter eu_verarbeitung verlangt jetzt, dass ALLE genannten
Länder EU/EWR sind. Neues Feld dpf_zertifiziert macht die EU-US Data
Privacy Framework-Zertifizierung strukturiert statt nur Fließtext.
Öffnet nebenbei den Weg für Katalogeinträge außerhalb EU/USA (z. B.
DeepSeek), die vorher am alten Wertebereich scheiterten.

Zusätzlich Typografie-Nachbesserung nach Nutzer-Feedback: Formular-
Abschnittsüberschriften (<legend>) saßen zu dicht am Kartenrand, weil
<legend> das Padding des umschließenden <fieldset> ignoriert - jetzt
mit eigenem Padding und größerer Schrift, h2/h3 einheitlich gesetzt.
v0.29.0
2026-08-31 13:52:00 +02:00
noroot
2e459f1dd1 docs: Werkzeugkatalog-Stand aktualisieren (19 Einträge, alle 12 Zweck-Kategorien), Schema-Lücke für Drittland-Hosting dokumentieren
Katalog um 9 weitere recherchierte Werkzeuge erweitert (Notion AI,
Grammarly Business, Midjourney, Julius AI, Synthesia, ElevenLabs,
Zendesk AI, Canva Magic Studio, Zapier) — deckt jetzt alle zwölf
Zweck-Kategorien ab. Midjourney bewusst als Negativbeispiel (kein AVV,
keine EU-Residenz, unbegrenzte Speicherung) aufgenommen statt
weggelassen. DeepSeek NICHT angelegt: verarbeitungsort erlaubt nur
EU/USA/gemischt/on-prem, keiner davon bildet China-Hosting korrekt ab
— Wertebereich müsste zuerst erweitert werden.
2026-08-31 13:17:13 +02:00
noroot
d77b19f61f feat: Zweck-Vokabular auf 12 Kategorien erweitert, Werkzeug-Formular im Enterprise-Look überarbeitet
Sechs neue Zweck-Kategorien (Datenanalyse, Video, Audio/Sprache,
Kundenservice/Chatbot, Präsentation/Design, Automatisierung/Agenten)
ergänzen die ursprünglichen sechs — deckt jetzt die tatsächliche
Bandbreite an Einsatzgebieten ab statt nur klassischer Wissensarbeit.

Werkzeug-Formular (Betreiber + Mandant, gleiche Vorlage) neu strukturiert:
Karten-Layout mit thematischen Abschnitten, 2-Spalten-Grid für kurze
Felder, Chip-Gruppen statt langer Checkbox-Listen, abgesetzte
Gefahrenzone für den Löschen-Button. Dabei einen vorbestehenden Bug
gefunden und behoben: das Lösch-Formular war fest auf die
Betreiber-Route verdrahtet, obwohl dieselbe Vorlage auch vom
Mandanten-Formular genutzt wird.
v0.28.0
2026-08-31 13:12:07 +02:00
noroot
dbeaa45644 feat: Ampel-Kennzeichnung für Prüfalter im Werkzeugkatalog, Scroll-Fade für breite Tabellen
Katalogtabellen zeigten bisher nur "nicht aktuell geprüft" als
Binärflag; jetzt eine dreistufige Ampel (aktuell/bald fällig/
abgelaufen) mit denselben Farbtoken wie die KI-VO-Risikoampel.
Zusätzlich: .table-scroll bekommt einen rechten Fade-Rand, damit eine
am Fensterrand abgeschnittene breite Tabelle als "hier geht's mit
Scrollen weiter" erkennbar ist statt wie ein Rendering-Fehler
auszusehen.
v0.27.0
2026-08-31 12:28:57 +02:00
noroot
4358dbaa0d feat: Werkzeugkatalog als Tabelle, Aufbewahrung nullable, kontrolliertes Zweck-Vokabular, Subprozessoren
Katalog (zentral + mandantenseitig) zeigt jetzt eine mehrspaltige
Tabelle statt einer Liste. aufbewahrung_tage ist nullable (NULL =
vom Anbieter nicht beziffert, unterscheidbar von echter 0-Tage-
Zusicherung). geeignete_zwecke ist Checkbox-Auswahl aus den sechs
Fragebogen-Zweck-Kategorien statt Freitext. Neues Feld
subprozessoren macht Unterauftragsverarbeiter durchsuchbar statt nur
Freitext in Einschränkungen. 180-Tage-Frische-Markierung in beiden
Katalogansichten ergänzt.
v0.26.0
2026-08-31 12:08:05 +02:00
noroot
7397f70068 feat: Firmen-CRUD für den Betreiber (anlegen, umbenennen)
Schließt eine echte Lücke: der Betreiber-Bereich konnte Accounts
bisher nur lesend anzeigen, eine neue Firma entstand ausschließlich
über die öffentliche Selbstregistrierung. GET/POST
/betreiber/accounts/neu legt jetzt eine Firma samt erstem admin-Login
direkt vom Betreiber aus an (z. B. für vertriebsunterstütztes
Onboarding oder Testkonten) — erzeugt einen audit_log-Eintrag. POST
/betreiber/accounts/{id}/umbenennen korrigiert den Firmennamen
(store.UpdateAccount). Bewusst kein Löschen: ein Hard-Delete würde
gegen die Fremdschlüssel aus antrag/app_user/audit_log laufen und
Historie zerstören — dasselbe Prinzip wie bei Nutzern (deaktivieren
statt löschen), ein Sperren/Deaktivieren für Accounts fehlt aber noch
und hängt an der noch nicht getroffenen Abrechnungsarchitektur.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
v0.25.0
2026-08-30 13:39:01 +02:00
noroot
2736e2c0db feat: Support-Login — Betreiber kann sich als Kunden-Nutzer anmelden
Ebene 5 (Betreiber) kann sich jetzt auf der Account-Detailseite über
einen Button je Nutzer als dieser Kunden-Login anmelden, ohne dessen
Passwort zu kennen — für Support-Fälle, in denen der Betreiber
nachvollziehen muss, was ein Kunde sieht. Nicht für deaktivierte
Nutzer möglich. Neue Spalte session.impersonated_by_user_id (Migration
0014, store.CreateImpersonatedSession) hält fest, wer die Sitzung
ausgelöst hat — die Nav zeigt während der gesamten Sitzung einen
auffälligen Banner ("Support-Zugriff durch ..."), damit nie unklar
ist, im Kontext eines fremden Kontos zu handeln. Jede Nutzung erzeugt
einen audit_log-Eintrag. Die neue Sitzung ersetzt die eigene
Betreiber-Sitzung (kein Sitzungs-Stack) — nach der Nutzung meldet sich
der Betreiber mit den eigenen Zugangsdaten neu an.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
v0.24.0
2026-08-30 00:38:40 +02:00
noroot
9d2cb79424 fix: Startseite zeigt echte Übersicht statt Phase-1-Platzhalter
handleIndex war seit dem Produktwechsel unverändert ein Platzhalter aus
Schritt 1 ("Antrag stellen"/"Meine Anträge" — beides bereits in der
Seitenleiste vorhanden, die Startseite selbst zeigte nichts Eigenes).
Zeigt jetzt die Zahl eigener offener Anträge, und für die Fachebene
zusätzlich die Zahl offener Fälle im Posteingang; Betreiber sehen
einen Link zur Plattform statt leerer Antrags-Kacheln, die für sie
ohnehin nicht gelten. Bewusst ohne Wiedervorlage-Zahl — deren
Berechnung vergleicht jeden Genehmigungs-Snapshot gegen den aktuellen
Katalogstand und wäre für eine bei jedem Login geladene Startseite zu
teuer.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
v0.23.0
2026-08-29 23:10:42 +02:00
noroot
4dd9bd88cb docs: fachliche Annahmen im Regelwerk bestätigt
Rangfolge der Datenklassen, auftragsdaten bei kein_training_auf_eingabe,
Vollständigkeit der drei "verboten"-Varianten und der intern-Fallback
wurden vom Produktverantwortlichen bestätigt (rules/OPEN.md). Weiterhin
offen: konkrete Löschfristen in Tagen, harte Filterung von
menschliche_aufsicht/kennzeichnungspflicht/dsfa_erforderlich.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-29 22:47:07 +02:00
noroot
96ac5d6b59 refactor: unbenutzte View-Felder entfernen (ID auf drei Templates nie referenziert)
Ein systematischer Audit nach demselben Muster wie beim Fragebogen-
Antworten-Bug fand drei Felder, die vom Handler befüllt, aber im
zugehörigen Template nie referenziert werden: antragDetailData.ID
(kein Formular auf der Seite braucht sie), anforderungView.ID (die
Beschreibung reicht als lesbare Anforderung, die interne Regel-ID ist
kein Nutzerinhalt), betreiberAccountDetailData.AccountID (die
Account-Detailseite ist rein lesend, kein Formular zeigt darauf).
Anders als der Fragebogen-Antworten-Bug ist hier keine für Nutzer
relevante Information verloren gegangen — die Felder waren schlicht
totes Gewicht, deshalb entfernt statt eine Anzeige dafür zu erfinden.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
v0.22.0
2026-08-29 21:07:00 +02:00
noroot
29961f5657 fix: Fragebogen-Antworten auf der Antrag-/Fall-Detailseite anzeigen
Die tatsächlich gegebenen Fragebogen-Antworten (B-Fragen zur Datenklasse,
C-Fragen zur KI-VO-Einstufung, D-Werkzeugangaben) wurden bisher nirgends
angezeigt — weder dem antragstellenden Mitarbeiter noch der Fachebene
beim Entscheiden, obwohl der Code-Kommentar an handleAntragDetail schon
"zeigt einen Antrag mit allen Antworten" versprach. Nur die daraus
abgeleitete Bewertung war sichtbar, nicht die Grundlage, auf der sie
beruht — im Audit nicht nachvollziehbar (siehe CLAUDE.md, Grundregel)
und für die Fachebene beim Entscheiden nicht praktikabel.

antwortenAnzeige() baut aus den rohen JSON-Antworten eine lesbare Liste
mit denselben Fragen-Labels wie im Fragebogen selbst — jetzt sowohl auf
GET /antraege/{id} (Ebene 2) als auch GET /faelle/{id} (Ebene 3)
sichtbar.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
v0.21.0
2026-08-29 18:11:25 +02:00
noroot
3ec57f2786 fix: admin bekommt Fachebene-Rechte (Posteingang, Entscheiden, Register, Wiedervorlage)
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>
v0.20.0
2026-08-29 17:58:06 +02:00
noroot
6884f28109 feat: CSV-Import für Mitarbeiter
Schließt den zweiten Punkt der geplanten Onboarding-Reihenfolge. Admin
lädt eine CSV mit Spalten email (Pflicht) und role (optional, Standard
mitarbeiter) unter GET /verwaltung/nutzer/import hoch. Wie beim
einzelnen Nutzer-Anlegen gibt es keine Einladungsmail — pro Zeile wird
ein Einmalpasswort erzeugt und nur auf der Ergebnisseite direkt nach
dem Import angezeigt, danach nirgendwo mehr abrufbar. Kein
Alles-oder-nichts-Import: eine ungültige Zeile blockiert nicht die
übrigen, jede Zeile bekommt ihr eigenes Ergebnis.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-29 17:52:02 +02:00
noroot
b91a58ccba feat: Einladungslink für die Mitarbeiter-Selbstanmeldung
Schließt den ersten Punkt der geplanten Onboarding-Reihenfolge (siehe
CLAUDE.md): ein Admin sieht unter GET /verwaltung/einladung einen
Sammellink (account.einladung_token, Migration 0013), den beliebig
viele Mitarbeitende nutzen können, um sich selbst mit eigenem Passwort
anzumelden (GET/POST /einladung/{token}, öffentlich, Rolle
mitarbeiter). Kein Ablaufdatum, aber per Admin jederzeit erneuerbar —
das macht den alten Link sofort ungültig, falls er versehentlich
außerhalb des Unternehmens geteilt wurde. Keine E-Mail-Bestätigung
(bewusst konsistent mit dem Rest des Produkts, das noch keine
E-Mail-Infrastruktur hat).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
v0.19.0
2026-08-29 17:41:36 +02:00
noroot
eb99891e38 feat: eigene Werkzeugkatalog-Einträge für Mandanten
Ein Mandant kann jetzt zusätzlich zum Sperren zentraler Katalogeinträge
auch eigene, nur für sich sichtbare Werkzeuge anlegen/bearbeiten/
löschen (GET/POST /verwaltung/werkzeuge/neu bzw. /{id}, POST
/{id}/loeschen — werkzeug.account_id = eigener Account). Nutzt
dasselbe Formular wie der zentrale Katalog des Betreibers
(werkzeugFormData/betreiber-werkzeug-form.html, ein neues ActionBase-
Feld unterscheidet die Ziel-URL); ein zentraler oder fremder Eintrag
bleibt über diese Route unerreichbar (404). Schließt die letzte
dokumentierte Lücke bei "eigene Werkzeug-Freigaben/-Sperrungen" (Ebene 4).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
v0.18.0
2026-08-29 15:23:43 +02:00
noroot
36af1bf288 feat: responsives Frontend — Seitenleiste ab Desktop, Tabellen-Styling
Nav wird jetzt für alle Seiten aus einer einzigen Stelle
(layout.html-Block) gerendert: auf dem Handy ein per Checkbox-Hack
aufklappbares Menü (kein JavaScript), ab 960px Breite per CSS in eine
permanente, dunkle Seitenleiste im enconf-Look verwandelt — dieselbe
Markup-Struktur bedient beide Layouts, kein separates Desktop-Template.
<details>/<summary> wurde bewusst NICHT verwendet: aktuelle
Chromium-Versionen steuern dessen Auf-/Zuklapp-Zustand über eine
CSS-resistente interne Animation (auch mit !important nicht
überschreibbar), per Headless-Screenshot verifiziert.

Zusätzlich: Tabellen (vorher komplett ungestylt, betraf v. a. das
Register) haben jetzt echte Gestaltung inkl. horizontal scrollbarem
Wrapper für schmale Bildschirme, Listenkarten ohne Link-Wrapper
(Nutzer-/Abteilungsliste) bekommen dieselbe Kartenoptik wie
verlinkte Einträge, und destruktive Aktionen (Löschen/Deaktivieren)
sind jetzt visuell von primären Aktionen unterschieden.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
v0.17.0
2026-08-29 15:06:14 +02:00
noroot
5f6502bb41 feat: Wiedervorlage und Katalog-Benachrichtigung (Schritt 7 der Baureihenfolge)
GET /wiedervorlage (Ebene 3) zeigt aktive Genehmigungen, die erneut
geprüft werden sollten: Ablaufdatum erreicht oder innerhalb von 30
Tagen, das zugesagte Werkzeug wurde aus dem Katalog entfernt, oder der
aktuelle Katalogeintrag weicht bei AVV/Training-Standard/
Verarbeitungsort vom zum Entscheidungszeitpunkt eingefrorenen Snapshot
ab. Keine E-Mail-Infrastruktur vorhanden — Benachrichtigung ist bewusst
eine In-App-Liste. Damit ist die ursprüngliche Baureihenfolge
(Schritt 1-7) vollständig umgesetzt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
v0.16.0
2026-08-29 14:12:44 +02:00
noroot
22f748d66c feat: Ebene 4 vollständig steuerbar machen — Abteilungen, Werkzeug-Sperrungen, Nutzer-Deaktivierung
Deklarix soll ein buchbarer Service werden — dafür muss jede Entität im
Datenmodell über das Frontend steuerbar sein, nicht nur einsehbar.
Schließt drei konkrete Lücken:

- Abteilungen (GET /verwaltung/abteilungen, anlegen/löschen) — ohne
  diese Seite blieb die Abteilung-Auswahl im Antrag-Fragebogen leer
  und unbenutzbar, das war ein Funktionsdefizit, kein Komfortfehler.
- Eigene Werkzeug-Sperrungen (GET /verwaltung/werkzeuge) — ein Mandant
  kann einen zentralen Katalogeintrag jetzt für sich sperren/entsperren,
  ohne den zentralen Katalog selbst zu verändern.
- Nutzer-Deaktivierung (POST /verwaltung/nutzer/{id}/deaktivieren bzw.
  .../aktivieren, neue Spalte app_user.active, Migration 0012). Nutzer
  werden nicht gelöscht (Fremdschlüssel auf antrag/entscheidung/
  audit_log würden das verhindern und die Historie zerstören) —
  deaktivierte Logins können sich nicht mehr anmelden und verlieren
  eine laufende Sitzung sofort. Ein Admin kann sich nicht selbst
  deaktivieren.

Zusätzlich: store.ListAktiveGenehmigungenForAccount als Grundlage für
die Wiedervorlage (Schritt 7, Web-Layer folgt).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-29 14:07:47 +02:00
noroot
d52b325424 feat: Registereintrag und Export (Schritt 6 der Baureihenfolge)
Jede Genehmigung (genehmigt/genehmigt_mit_auflagen) erzeugt jetzt
automatisch einen append-only registereintrag mit Zweck, Abteilung,
Werkzeug, Datenklasse, Einstufung, Auflagen, Verantwortliche/r und
Gültigkeit. Ebene 3 (verantwortlicher/pruefer) kann das Register unter
GET /registereintraege einsehen und als CSV oder PDF exportieren. Der
PDF-Export nutzt das bereits vorhandene, bisher ungenutzte go-pdf/fpdf
(keine neue Abhängigkeit) mit UnicodeTranslatorFromDescriptor für
deutsche Umlaute. Abgelehnte Entscheidungen und Rückfragen erzeugen
bewusst keinen Registereintrag.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
v0.15.0
2026-08-29 12:55:24 +02:00
noroot
cff8c9eadf feat: Werkzeugkatalog-Pflege für die Plattform (Ebene 5)
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>
v0.14.0
2026-08-29 12:42:48 +02:00
noroot
aad517ce23 feat: Nutzerverwaltung für Ebene 4 (Mandanten-Verwaltung)
Ein admin kann jetzt weitere Logins im eigenen Mandanten anlegen
(GET /verwaltung/nutzer, GET/POST /verwaltung/nutzer/neu) mit einer
der vier Mandanten-Rollen — betreiber bleibt Ebene 5 vorbehalten und
kann von keinem Mandanten-Admin vergeben werden. Schließt die Lücke,
dass die Fachebene (verantwortlicher/pruefer) bisher nur per
manuellem SQL-Insert erreichbar war, weil die Firma-Registrierung
ausschließlich einen admin-Nutzer erzeugt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
v0.13.0
2026-08-29 12:34:04 +02:00
noroot
d1e80dd19f feat: Posteingang und Entscheiden für die Fachebene (Schritt 5 der Baureihenfolge)
Ebene 3 (Rollen verantwortlicher/pruefer) bekommt GET /faelle
(Posteingang aller offenen Anträge des Mandanten) und GET/POST
/faelle/{id} zum Entscheiden. Eine Entscheidung friert Regelwerk-,
Katalog- und den vollständigen Werkzeugdatensatz ein, verlangt eine
Begründung bei Abweichung vom abgeleiteten Vorschlag, erlaubt bei
Genehmigung nur ein durch die Bewertung zulässiges Werkzeug und
protokolliert die Entscheidung im Audit-Log. entscheidung ist
append-only wie bewertung.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
v0.12.0
2026-08-29 12:26:51 +02:00
noroot
e968cf9761 feat: Ergebnisdarstellung mit Herleitung (Schritt 4 der Baureihenfolge)
POST /antraege berechnet und speichert direkt beim Einreichen eine
append-only bewertung (Datenklasse, KI-VO-Einstufung, Anforderungen,
zulässige/ausgeschlossene Werkzeuge), GET /antraege/{id} zeigt sie
inklusive Herleitung und dem Pflicht-Hinweis, dass das System nicht
entscheidet, sondern nur vorbereitet.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
v0.11.0
2026-08-29 10:22:24 +02:00
noroot
b1b121bb23 feat: Ableitungen und harte Filter (Schritt 3 der Baureihenfolge)
Reine Auswertungslogik in internal/rules, operiert auf rules.Antworten
(geparst aus antrag.antworten) und den drei Regelwerken aus Schritt 1
— ohne DB-/Web-Zugriff, vollständig isoliert testbar.

- EvaluateDatenklasse: "höchste zutreffende Stufe gewinnt" (höherer
  Rang). Trifft keine Stufe zu (z. B. B7 fälschlich "nein" trotz keiner
  anderen Kategorie), wird konservativ "intern" angenommen statt
  "oeffentlich" — im Zweifel mehr Schutz. Nicht fachlich bestätigt,
  siehe rules/OPEN.md Punkt 5.
- EvaluateEinstufung: Prüfreihenfolge wie im Regelwerk (verboten zuerst
  = K.-o.-Prüfung), erste zutreffende Stufe/Variante gewinnt.
  IstVerboten prüft die K.-o.-Bedingung direkt.
- DeriveAnforderungen: Anforderungsprofil aus Datenklasse+Einstufung.
- FilterWerkzeuge/ErfuelltAnforderung: harter Filter gegen einen
  Werkzeugkatalog. WerkzeugEigenschaften ist ein eigener, schlanker Typ
  statt store.Werkzeug — internal/rules bleibt unabhängig von
  internal/store. Nur technische Werkzeug-Eigenschaften
  (avv_erforderlich, eu_verarbeitung, kein_training_auf_eingabe) werden
  hart gefiltert; Prozess-Anforderungen (menschliche_aufsicht,
  kennzeichnungspflicht, dsfa_erforderlich) sortieren kein Werkzeug
  aus, sondern werden später als Auflage vermerkt — Annahme, siehe
  rules/OPEN.md Punkt 6.

rules/OPEN.md um Punkt 4 (Auswirkung der fehlenden Löschfristen auf den
Filter), Punkt 5 (Datenklasse-Fallback) und Punkt 6 (welche
Anforderungen hart filtern) ergänzt — Annahmen dokumentiert statt
geraten, damit Schritt 3 nicht auf die fachliche Klärung warten musste.

16 neue Tests gegen die ECHTEN rules/*.yaml-Dateien (nicht nur
synthetische Fixtures) — deckt Rang-Konflikte, "Unsicher zählt wie Ja",
alle vier Einstufungsstufen inkl. Auffangregel, Anforderungs-Ableitung
und Werkzeug-Filterung ab. Noch nicht ans Web angebunden (Schritt 4).
v0.10.0
2026-08-29 09:39:41 +02:00
noroot
db9c5b7482 feat: Fragebogen mit adaptiver Logik (Schritt 2 der Baureihenfolge)
Ebene 2 ("Antrag stellen") als einseitiges Formular statt mehrseitigem
Assistenten — jede zusätzliche Seite kostet Zeit, und laut Spezifikation
wird ein Antrag umgangen, wenn er länger als fünf Minuten dauert.

- GET /antraege/neu: Fragebogen-Formular (Abschnitte A-D exakt nach
  Spezifikation). Adaptive Folgefragen (C2: welche Art von Entscheidung,
  C3: welche Art von Erkennung) werden rein per CSS :has() ein-/
  ausgeblendet, kein JavaScript nötig — visuell mit Chromium-Screenshots
  verifiziert (unchecked vs. checked).
- POST /antraege: legt an und reicht direkt ein (entwurf->eingereicht in
  einem Schritt, kein Zwischenspeichern als Entwurf für v1). Die
  antworten-JSON nutzt exakt dieselben Fakten-Schlüssel wie
  rules/*.yaml (b1-b7, c1-c5, c2_folge, c3_art) — Schritt 3 kann sie
  direkt auswerten, ohne Felder umzubenennen. "Unsicher" wird bewusst
  NICHT zu "ja" normalisiert (das ist eine Auswertungsregel für Schritt
  3, keine Speicherregel) — der Antrag hält fest, was der Mitarbeiter
  tatsächlich geantwortet hat.
- GET /antraege: eigene Anträge (Ebene 2 sieht nur eigene, nicht die
  des ganzen Mandanten — dafür neue Store-Methode
  ListAntraegeForUser, getrennt von ListAntraegeForAccount für den
  späteren Ebene-3-Posteingang).
- GET /antraege/{id}: Detail, fremder Antrag liefert 404 (nicht 403,
  gleiches Muster wie überall sonst im Projekt).
- betreiber-Rolle wird von allen Antrags-Routen weggeleitet (Ebene 5
  ist technisch getrennt vom Mandantenbereich).

rules/OPEN.md Punkt 3 (fehlende Fragebogen-Unterscheidung für die
"verboten"-Varianten) damit geklärt: c3_art existiert jetzt mit den
bereits in rules/kivo_einstufung.yaml erwarteten Werten.

Volle Testsuite inkl. echter Postgres-Tests grün; End-to-End gegen
einen laufenden Server verifiziert (Antrag anlegen, antworten-JSON in
der DB korrekt, Detail-/Listenansicht, Mandantentrennung).
v0.9.0
2026-08-28 23:09:24 +02:00
noroot
344a33805a fix: Migration 0008 auf Bestandsdaten mit alten Rollenwerten vorbereiten
Beim ersten Deploy-Versuch auf dem Testsystem schlug die neue
app_user_role_check-Constraint fehl, weil echte Bestandszeilen noch
alte Rollenwerte (admin, creator) trugen — die Migration ging naiv
davon aus, dass es keine bestehenden Nutzer mit alten Rollen gäbe.

Fix: alte Constraint zuerst droppen (sie kennt neue Werte wie
"betreiber" noch nicht und hätte die Datenmigration selbst blockiert),
dann bestehende Zeilen umschreiben (admin -> betreiber, da das die
alte Plattform-Betreiber-Rolle war, nicht der neue mandantenbezogene
admin; creator/agentur/marke/kanzlei -> mitarbeiter), erst danach die
neue Constraint scharf schalten. Lokal gegen eine Datenbank mit
manuell eingefügten Alt-Rollen-Zeilen verifiziert (0001-0007 anwenden,
Testdaten einfügen, 0008 anwenden, Rollen prüfen).
v0.8.1
2026-08-28 21:43:04 +02:00
noroot
b4d4ee8d3c 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.
v0.8.0
2026-08-28 21:39:04 +02:00
noroot
6fd7831784 feat: Erinnerung zum Sichern von Story-Insights vor Ablauf
Ausgangspunkt: Instagram hält Story-Insights nach eigener Aussage nur
24 Stunden vor, auch der offizielle Datenexport enthält sie nicht mehr
danach. Der Standbild-Screenshot beim Prüfen entsteht direkt beim
Veröffentlichen, bevor nennenswerte Kennzahlen existieren — er kann
das strukturell nicht auffangen. Eine OAuth-Anbindung allein löst das
auch nicht: selbst mit API-Zugriff bräuchte es einen Abruf innerhalb
desselben 24h-Fensters.

- Migration 0007: asset.purpose ('initial' | 'insights', Default
  'initial' erhält die Bedeutung aller Bestandszeilen). Ein Beitrag
  kann jetzt mehrere Insights-Nachweise über die Zeit bekommen.
  GetLatestAssetForSubmission berücksichtigt weiterhin nur 'initial',
  damit ein späterer Insights-Upload nie den beim Archivieren
  referenzierten Original-Screenshot verdrängt.
- internal/web/insights_reminder.go: computeInsightsReminder — reine,
  ungetestete gegen echte Instagram-Daten, aber isoliert testbare
  Logik fürs Erinnerungs-Timing (Produktentscheidung, keine Rechtsnorm,
  daher nicht in rules/*.yaml).
- GET /beitraege/{id} zeigt die Erinnerung bei veröffentlichten
  "story"-Beiträgen ohne existierendes insights-Asset; POST
  /beitraege/{id}/insights speichert einen weiteren Screenshot (gleiche
  Validierung wie das initiale Standbild, wiederverwendet über
  readUploadedAsset/storeAsset mit purpose-Parameter).
- Bewusst nur In-App-Banner in dieser Ausbaustufe, kein Mail-/Push-
  Versand — dafür fehlt aktuell ein SMTP-Relay/Versanddienst, siehe
  CLAUDE.md-Hinweis dazu.

Volle Testsuite inkl. echter Postgres-Tests grün; End-to-End gegen
einen laufenden Server verifiziert (Story archivieren → Erinnerung
sichtbar → Insights-Upload → Erinnerung verschwindet, Nachweis
gelistet, Mandantentrennung beim Upload durchgesetzt).
v0.7.0
2026-08-28 10:10:44 +02:00
noroot
5813e6209c feat: technisches Grundgerüst für Instagram-/TikTok-OAuth (Plattform-Verbindung)
Vorbereitung für automatische Beweissicherung statt manuellem
Screenshot-Upload: ein Kunde kann künftig seinen eigenen Instagram-
oder TikTok-Account per Standard-OAuth-Consent verbinden. Bewusst nur
das Grundgerüst — Meta/TikTok verlangen vor öffentlicher Nutzung eine
einmalige Business-Verification/App-Review (Wochen Vorlauf, siehe
CLAUDE.md-Abschnitt "Plattform-Verbindung (OAuth)"), die separat von
dieser Codeänderung läuft.

- internal/socialconnect: Connector-Interface + InstagramConnector/
  TikTokConnector (reiner Authorization-Code-Flow, kein DB-Zugriff).
  Instagram tauscht den Code zweistufig (kurzlebiges → 60-Tage-Token),
  TikTok liefert Access-/Refresh-Token direkt. Endpunkte/Scopes wurden
  gegen aktuelle Entwicklerdokumentation gebaut, nie gegen die echte
  API verifiziert (keine Zugangsdaten vorhanden) — Hinweis dazu im
  Paket- und CLAUDE.md-Kommentar.
- Migration 0006: platform_connection (NICHT append-only, anders als
  finding/extraction/asset — ein Token wird ersetzt, keine Korrektur-
  Zeile), höchstens eine Verbindung pro Account+Plattform.
- internal/web: GET /verbindungen (Übersicht je Plattform: verbunden/
  nicht verbunden/nicht konfiguriert), GET /oauth/{platform}/start
  (State-Cookie gegen CSRF, Redirect zum Consent-Screen),
  GET /oauth/{platform}/callback (State prüfen, Code tauschen,
  Verbindung speichern), POST /verbindungen/{platform}/trennen.
- Ohne gesetzte Client-Credentials + PUBLIC_BASE_URL bleibt die
  Funktion inaktiv (kein Connector konfiguriert, /verbindungen zeigt
  "nicht konfiguriert", kein Absturz) — main.go loggt das beim Start.

Volle Testsuite inkl. echter Postgres-Tests grün; OAuth-Flow gegen
Fake-Connector/httptest-Server verifiziert (State-Mismatch, Ablehnung
durch Nutzer, Token-Speicherung, Mandantentrennung). Kein Live-Test
gegen echte Meta-/TikTok-Endpunkte möglich, da noch keine echten
Client-Credentials existieren.
v0.6.0
2026-08-28 09:32:43 +02:00
noroot
790ab20651 feat: Standbild-Upload bei der Pre-Publish-Prüfung
CLAUDE.md beschreibt die Prüfung seit dem ersten Commit als "Caption,
Standbild und Vertragslage rein" — bisher wurde nur die Caption
verarbeitet, das asset-Schema aus Migration 0001 blieb ungenutzt.

- internal/store/asset.go: CreateAsset/GetLatestAssetForSubmission.
  Migration 0005 macht asset append-only (Trigger fehlte seit 0001,
  weil bis jetzt nichts hineinschrieb) — ein hochgeladenes Beweisstück
  wird nicht nachträglich ausgetauscht, aus demselben Grund wie bei
  extraction/finding/evidence_package.
- handleCheck liest ein optionales "standbild"-Formularfeld (Bild-
  Upload, max. 8 MiB, Content-Type muss image/* sein), validiert es
  VOR dem Anlegen der Submission (ein ungültiger Upload hinterlässt so
  keine leere Beitrags-Zeile), speichert es danach unter ASSET_DIR und
  legt die Asset-Zeile an.
- handleArchive bindet den Asset-Hash (falls vorhanden) in den
  Metadaten-Hash und ins PDF-Dossier ein (dossier.Data.AssetHash war
  bereits vorbereitet, wurde aber nie befüllt).
- index.html: Formular auf multipart/form-data umgestellt
  (hx-encoding + enctype), neues optionales Dateifeld. handleCheck
  bleibt abwärtskompatibel zu urlencoded-Requests (ParseMultipartForm
  liefert ErrNotMultipart, das wird wie "kein Bild hochgeladen"
  behandelt, nicht wie ein Fehler).
- ASSET_DIR neue Konfigurationsvariable (Default "assets", wie
  DOSSIER_DIR relativ zu WorkingDirectory=/var/lib/deklarix — kein
  postinst-Healing nötig, anders als bei RULES_DIR, dessen Default
  nicht zum installierten Pfad passt).

Volle Testsuite inkl. echter Postgres-Tests grün; End-to-End gegen
einen laufenden Server verifiziert (Upload, Hash in DB, Hash im
erzeugten PDF via pdftotext, Ablehnung bei falschem Dateityp).
v0.5.0
2026-08-27 21:45:03 +02:00
noroot
ae59e745c0 fix: Admin nach Login direkt zum Admin-Bereich leiten
Ein Admin-Login hat kein eigenes Produkt zu prüfen — die Pre-Publish-
Prüfung ("/") ist die Startseite für Mandanten (Creator/Agentur/Marke/
Kanzlei). Nutzer hat nach dem Login zu Recht gefragt, wieso ein Admin
dieses Formular sieht. Login leitet role=admin jetzt direkt zu /admin,
alle anderen Rollen weiterhin zu "/".
v0.4.2
2026-08-27 19:06:05 +02:00
noroot
8e5d06aafb fix: Admin-Link in der Navigation zeigen
Nach dem Login landet jeder Nutzer (auch ein Admin, jeder Login gehört
zu einem Account) auf der normalen Startseite — ohne einen Link zu
/admin in der Navigation war der Admin-Bereich für einen Admin ohne
die URL im Kopf praktisch unerreichbar (genau das hat der Nutzer nach
dem Login gemeldet).

navData{IsAdmin} wird jetzt von jeder angemeldeten Seite (Start,
Beiträge, Beitrag-Detail, alle Admin-Seiten) an den gemeinsamen
"nav"-Template-Block durchgereicht; der Link erscheint nur für
role=admin. Test deckt beide Fälle ab (Admin sieht den Link, Mandant
nicht).
v0.4.1
2026-08-27 19:04:04 +02:00
noroot
835ad9f0a7 feat: Admin-Bereich (Accounts, Kanzlei-Verzeichnis-Freigabe, Audit-Log)
Bislang gab es keine vom Nutzer-Rollenmodell (creator/agentur/marke/
kanzlei) getrennte Betreiber-Rolle — jede Verwaltungsaufgabe (welche
Kanzlei darf im öffentlichen Verzeichnis stehen, wer sind unsere
Accounts) wäre nur per Hand in der Datenbank möglich gewesen. Admin
ist von Anfang an als fünfte app_user-Rolle im Datenmodell verankert,
nicht nachträglich aufgesetzt.

Migration 0004:
- app_user.role erlaubt zusätzlich 'admin' (kein Self-Service-Weg
  dorthin — /register bietet die Rolle nicht an, erster Admin wird
  einmalig per SQL angelegt, siehe CLAUDE.md).
- account.verified: Freigabe fürs kostenlose Kanzlei-Verzeichnis
  (§ 49b Abs. 3 BRAO: reine Auflistung, kein Routing/keine Vermittlung).
- audit_log: append-only-Protokoll jeder Admin-Aktion (gleicher Trigger
  wie finding/extraction/evidence_package).

Neue Routen:
- GET /admin, /admin/accounts, /admin/accounts/{id}: Accounts-Übersicht
  und -Detail (Logins je Account), requireAdmin (404 statt 403 für
  angemeldete Nicht-Admins, wie beim bestehenden Mandanten-404-Muster).
- POST /admin/accounts/{id}/verifizieren: Kanzlei-Freigabe umschalten,
  schreibt einen Audit-Log-Eintrag.
- GET /admin/audit-log: Protokoll ansehen.
- GET /kanzleien: öffentliches Verzeichnis (kein Login), zeigt nur
  Accounts, die sowohl verified sind als auch einen Nutzer der Rolle
  "kanzlei" haben.

Volle Testsuite inkl. echter Postgres-Tests grün; End-to-End manuell
gegen einen laufenden Server verifiziert (Admin-Login, Verify-Toggle,
Erscheinen im öffentlichen Verzeichnis, Audit-Log-Eintrag, 404 für
Nicht-Admin-Zugriff).
v0.4.0
2026-08-27 18:17:45 +02:00
noroot
c9d71b0d54 feat: Archiv-Übersicht und Beteiligten-CRUD im Web-Layer
Bisher gab es nur Prüfen -> Archivieren, keine Möglichkeit, bereits
geprüfte Beiträge wieder anzusehen oder die Verantwortungsmatrix
(Beteiligte: wer hat vorgegeben, wer freigegeben) tatsächlich zu
pflegen — nur der Store-Layer dafür existierte schon.

Neu:
- GET /beitraege: Liste aller Beiträge des angemeldeten Mandanten
  (Plattform, Status, höchste Finding-Schwere) via
  ListSubmissionsForAccount.
- GET /beitraege/{id}: Detailseite mit Fakten, Findings, Archivieren-
  Aktion und Verantwortungsmatrix.
- POST .../beteiligte, .../beteiligte/{pid}/aktualisieren,
  .../beteiligte/{pid}/loeschen: echtes CRUD statt nur Ansicht, per
  htmx ohne Seiten-Reload.

Mandantentrennung wie beim bestehenden Archiv-Download: fremde
Beiträge und fremde Beteiligte (auch über eine erratene participant_id)
liefern 404, nicht 403 — sonst würde eine 403 die Existenz der Ressource
bei einem anderen Mandanten bestätigen.

Web-Store-Interface um die bereits fertigen Store-Methoden erweitert,
fakeStore in server_test.go entsprechend nachgezogen. Volle Testsuite
inkl. echter Postgres-Tests (./scripts/test.sh) grün.
v0.3.0
2026-08-27 17:54:33 +02:00
noroot
6df1d2961b feat: add participant CRUD and per-account submission listing
Participant (the Verantwortungsmatrix — who briefed, who approved) is
not append-only like finding/extraction/evidence_package; getting a
role wrong and correcting it isn't rewriting evidence, so full CRUD is
legitimate here: Create/List/Get/Update/Delete. UpdateParticipant sets
approved_at the first time freigegeben flips to true and never moves it
again on subsequent no-op updates — it marks when approval first
happened, not "last touched".

ListSubmissionsForAccount is the query the upcoming archive overview
needs: every submission for a tenant plus a findings count and highest
severity, computed with the same anti-join ListCurrentFindings already
uses for "currently valid" findings.

Also fixed a real bug this surfaced: CreateFinding let a nil Sources
slice reach a NOT NULL TEXT[] column, which Postgres rejects with an
unhelpful constraint error instead of a clear message. It now normalizes
nil to an empty slice before inserting — matters for any future rule
that ships without a fundstelle entry.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-27 17:46:31 +02:00