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).
521 lines
22 KiB
Markdown
521 lines
22 KiB
Markdown
# 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`
|
||
|
||
---
|
||
|
||
## Was das Projekt ist
|
||
|
||
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.
|
||
|
||
---
|
||
|
||
## Kernprinzip: Fakten trennen von Urteil
|
||
|
||
**Stufe 1 bestimmt Fakten (deterministisch, kein LLM). Das Regelwerk in
|
||
Go fällt das Urteil.**
|
||
|
||
- **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.
|
||
|
||
---
|
||
|
||
## Stack
|
||
|
||
Bewusst langweilig, ein Binary, lokal lauffähig:
|
||
|
||
| 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`) |
|
||
|
||
Kein Docker für die App selbst, kein React, keine Queue. Neue
|
||
Abhängigkeiten nur nach Rückfrage.
|
||
|
||
**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).
|
||
|
||
**Pfad:** `/var/www/deklarix` | **Git:** `https://git.netcell-it.de/projekte/deklarix` | **Branch:** `main`
|
||
|
||
---
|
||
|
||
## Projektstruktur
|
||
|
||
```
|
||
/var/www/deklarix/
|
||
├── cmd/
|
||
│ └── deklarix/
|
||
│ └── main.go # Entry Point, 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)
|
||
│ ├── store/ # Postgres, Migrationen
|
||
│ └── web/ # Handler, Templates
|
||
├── rules/ # YAML-Regeln, versioniert im Git
|
||
├── testdata/
|
||
│ └── golden/ # Golden-Tests: Beispielbeiträge + erwartetes Ergebnis
|
||
├── design/
|
||
│ └── enterprise.css # Design-System (enconf-Basis)
|
||
├── 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
|
||
|
||
Plus drei Tabellen für Auth/Mandantentrennung (`account`, `app_user`,
|
||
`session`), mehr braucht der MVP nicht:
|
||
|
||
- `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 `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.
|
||
|
||
**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.
|
||
|
||
**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. 2–4 Wochen; TikTok-Audit: ca. 1–2
|
||
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).
|
||
|
||
---
|
||
|
||
## 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.
|
||
|
||
**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.
|
||
|
||
---
|
||
|
||
## 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,
|
||
# 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")
|
||
|
||
# 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 oder Golden-Fall, 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.
|
||
|
||
## 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
|
||
|
||
- 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
|