Compare commits

...
Author SHA1 Message Date
simandClaude Sonnet 5.5 86240d0e31 ai-app: classify, doctype, highlight, Konfiguration, Korrekturen, Embeddings, Vorlagen und Summary-Qualitaet verdrahtet
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 3s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Failing after 34s
CI / Web (dms-browser) (push) Failing after 2m33s
CI / Web (shl) (pull_request) Failing after 14s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Successful in 16m30s
DB-Integrationstests / db-tests (pull_request) Waiting to run
CI / Web (dms-browser) (pull_request) Failing after 1m55s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Failing after 3s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 17s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Successful in 16s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Successful in 15m52s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Successful in 21m9s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Failing after 1m58s
AI-Pflichttest-Gate / pflichttest-gate (pull_request) Successful in 10m43s
CI / Core-Modul (Build/Vet/Test) (push) Successful in 50s
CI / Web (pdf-viewer) (pull_request) Failing after 1m18s
CI / Core-Modul (Build/Vet/Test) (pull_request) Successful in 45s
CI / Web (shl) (push) Failing after 22s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Failing after 58s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Failing after 1m8s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 4s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Successful in 3s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 3s
CI / Web (pdf-viewer) (push) Failing after 1m37s
Supply-Chain-Scan (Go) / govulncheck (push) Failing after 1m4s
Neues Paket featureapi mit Routen fuer zehn bisher nicht erreichbare AI-Pakete.
notraining und refcorpus haben keine Laufzeitfunktion.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-07 23:51:59 +01:00
patrickandClaude Sonnet 5.5 b9dbfe8678 ai-app: suggestapi verdrahtet (Titel, Korrespondent, Tags, Uebernahme, Mandantenkonfiguration)
CI / Web (dms-browser) (pull_request) Failing after 59s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Successful in 5m41s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Successful in 5m58s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Failing after 2s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Successful in 3s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 3s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Failing after 1m55s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Successful in 5m51s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 10m41s
CI / Web (pdf-viewer) (push) Failing after 37s
AI-Pflichttest-Gate / pflichttest-gate (pull_request) Successful in 5m11s
CI / Web (dms-browser) (push) Failing after 50s
Supply-Chain-Scan (Go) / govulncheck (push) Failing after 22s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 2s
CI / Core-Modul (Build/Vet/Test) (pull_request) Successful in 39s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Successful in 2s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Failing after 1m19s
DB-Integrationstests / db-tests (pull_request) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
CI / Web (shl) (push) Failing after 9s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 13s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Failing after 14s
CI / Web (shl) (pull_request) Failing after 9s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (push) Successful in 38s
CI / Web (pdf-viewer) (pull_request) Failing after 41s
Neues Paket suggestapi mit Service-Token-Pflicht. Sprachregister aus
NEXARCH_AI_LANGUAGES_FILE beim Start geladen. Uebernahme-Protokollierung
nur mit NEXARCH_AI_APP_CORE_BASE_URL, sonst 503.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-06 23:02:21 +01:00
patrick a3d5428c22 Merge branch 'feat/ai-eng09-gemini' into feat/ai-app-vorschlaege 2026-11-06 23:00:55 +01:00
patrick fcd021fb31 Merge branch 'feat/ai-cls09-herkunftsmarkierung' into feat/ai-app-vorschlaege 2026-11-06 23:00:55 +01:00
patrickandClaude Sonnet 5.5 037efeddb2 ai: Pruefprotokoll CLS-09
CI / Web (shl) (pull_request) Failing after 9s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Successful in 5m49s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Failing after 2s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 3s
CI / Web (shl) (push) Failing after 8s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Failing after 1m44s
CI / Core-Modul (Build/Vet/Test) (pull_request) Successful in 45s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Successful in 3s
CI / Web (pdf-viewer) (pull_request) Failing after 41s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Failing after 1m23s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Successful in 5m28s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
CI / Web (dms-browser) (pull_request) Failing after 57s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 4s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 3s
DB-Integrationstests / db-tests (pull_request) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 16s
CI / Web (dms-browser) (push) Failing after 54s
CI / Web (pdf-viewer) (push) Failing after 38s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Failing after 27s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Failing after 16s
CI / Core-Modul (Build/Vet/Test) (push) Successful in 42s
AI-Pflichttest-Gate / pflichttest-gate (pull_request) Successful in 1m34s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Successful in 3s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Successful in 2m2s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-06 22:59:06 +01:00
patrickandClaude Sonnet 5.5 5f984e87a7 ai/provider: Opt-in-Test und Pruefprotokoll ENG-09
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Successful in 2s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Failing after 3s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Failing after 1m50s
CI / Core-Modul (Build/Vet/Test) (push) Successful in 47s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
CI / Web (shl) (pull_request) Failing after 8s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Failing after 1m21s
CI / Web (dms-browser) (push) Failing after 52s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Successful in 5m24s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 3s
CI / Web (pdf-viewer) (push) Failing after 44s
CI / Web (pdf-viewer) (pull_request) Failing after 42s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Successful in 1m43s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Successful in 3s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 3s
CI / Web (shl) (push) Failing after 8s
Supply-Chain-Scan (Go) / govulncheck (push) Failing after 17s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Successful in 5m28s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 15s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 3s
CI / Core-Modul (Build/Vet/Test) (pull_request) Successful in 49s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Failing after 16s
CI / Web (dms-browser) (pull_request) Failing after 56s
AI-Pflichttest-Gate / pflichttest-gate (pull_request) Successful in 5m59s
DB-Integrationstests / db-tests (pull_request) Canceled after 0s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-06 22:59:04 +01:00
patrickandClaude Sonnet 5.5 2662062dc5 ai/doclang: Erkennungs-Testset und Pruefprotokoll CLS-08
CI / Web (dms-browser) (pull_request) Failing after 53s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Failing after 1m46s
CI / Web (dms-browser) (push) Failing after 56s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (pull_request) Successful in 38s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 3s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Failing after 1m18s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Failing after 12s
CI / Web (shl) (pull_request) Failing after 8s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Failing after 3s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (push) Successful in 38s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 3s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Successful in 2s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Successful in 3s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Successful in 5m14s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 2s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 12s
Supply-Chain-Scan (Go) / govulncheck (push) Failing after 19s
CI / Web (pdf-viewer) (push) Failing after 50s
CI / Web (pdf-viewer) (pull_request) Failing after 41s
DB-Integrationstests / db-tests (pull_request) Canceled after 0s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Successful in 1m45s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Successful in 1m54s
AI-Pflichttest-Gate / pflichttest-gate (pull_request) Successful in 4m56s
CI / Web (shl) (push) Failing after 10s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-06 22:58:30 +01:00
patrick 512309fae6 Merge branch 'feat/ai-titel-korrespondent' into feat/ai-cls08-mehrsprachig 2026-11-06 22:58:14 +01:00
patrickandClaude Sonnet 5.5 eebc87c4c1 ai: Realtests und Pruefprotokolle CLS-06 und CLS-07
CI / Web (shl) (pull_request) Failing after 9s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Failing after 1m17s
CI / Web (pdf-viewer) (push) Failing after 36s
Supply-Chain-Scan (Go) / govulncheck (push) Failing after 17s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Failing after 6s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 3s
CI / Web (dms-browser) (pull_request) Failing after 58s
CI / Web (pdf-viewer) (pull_request) Failing after 41s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Failing after 32s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 14s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (pull_request) Successful in 43s
DB-Integrationstests / db-tests (pull_request) Canceled after 0s
AI-Pflichttest-Gate / pflichttest-gate (pull_request) Successful in 5m2s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Successful in 5m5s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Successful in 4m55s
CI / Web (dms-browser) (push) Failing after 56s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 3s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Failing after 13s
CI / Web (shl) (push) Failing after 6s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Successful in 3s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 3s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Successful in 3s
CI / Core-Modul (Build/Vet/Test) (push) Successful in 45s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Successful in 5m5s
Prompt fuer unbekannte Korrespondenten praezisiert (Realtest 3/4 auf 4/4).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-06 22:57:59 +01:00
patrickandClaude Sonnet 5.5 db0b1f7266 ai/doclang: Sprachbewusste Analyse (CLS-08)
CI / Web (dms-browser) (push) Failing after 56s
CI / Web (pdf-viewer) (push) Failing after 46s
CI / Web (shl) (push) Failing after 8s
Supply-Chain-Scan (Go) / govulncheck (push) Failing after 18s
CI / Core-Modul (Build/Vet/Test) (push) Successful in 48s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 16s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Failing after 28s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 3s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 3s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 2s
Spracherkennung per Stoppwortliste, Registry per JSON-Datei erweiterbar
(NEXARCH_AI_LANGUAGES_FILE), Ausgabesprache je Mandant live aus der DB.
tagsuggest nimmt die Ausgabesprache in den Prompt, bestehende Tags
anderer Sprache behalten Vorrang.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-06 22:52:13 +01:00
patrickandClaude Sonnet 5.5 5347963771 ai/originmark: Herkunftsmarkierung fuer uebernommene KI-Zuordnungen (CLS-09)
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 3s
CI / Web (dms-browser) (push) Failing after 53s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 3s
CI / Web (pdf-viewer) (push) Failing after 43s
Supply-Chain-Scan (Go) / govulncheck (push) Failing after 18s
CI / Core-Modul (Build/Vet/Test) (push) Successful in 43s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 14s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 3s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Failing after 30s
CI / Web (shl) (push) Failing after 10s
Markierung je Mandant konfigurierbar, nur bei KI-Uebernahme, ohne
Konfiguration keine Markierung. KI-Uebernahmen gehen mit der Markierung
ins Entscheidungs-Audit.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-06 22:50:30 +01:00
patrickandClaude Sonnet 5.5 523042509d ai/provider: Gemini-Adapter hinter dem Provider-Interface
CI / Web (shl) (push) Failing after 7s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 3s
Supply-Chain-Scan (Go) / govulncheck (push) Failing after 19s
CI / Web (pdf-viewer) (push) Failing after 41s
CI / Core-Modul (Build/Vet/Test) (push) Successful in 44s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Failing after 28s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 3s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 3s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 15s
CI / Web (dms-browser) (push) Failing after 1m1s
Chat, Completion und Embedding ueber die Gemini-REST-API, Schluessel nur
per NEXARCH_AI_GEMINI_API_KEY (Header, nie URL). Ohne Schluessel
deaktiviert. Vertragstest gegen Fake-Server.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-06 22:48:45 +01:00
patrickandClaude Sonnet 5.5 7818c5464f ai: Titel- und Korrespondent-Vorschlag (titlesuggest, correspondentsuggest)
CI / Web (dms-browser) (push) Failing after 1m4s
Supply-Chain-Scan (Go) / govulncheck (push) Failing after 18s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 3s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Failing after 29s
CI / Core-Modul (Build/Vet/Test) (push) Successful in 50s
CI / Web (shl) (push) Failing after 9s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 3s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 2s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 19s
CI / Web (pdf-viewer) (push) Failing after 44s
Vorschlagen mit Konfidenz, keine automatische Uebernahme. Korrespondent
wird auf bestehende Schreibweise normalisiert.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-06 20:15:59 +01:00
patrick 998aaf964c Merge pull request 'connect: DATEV-Export in connect-app registrieren' (#429) from feat/connect-datev-registrieren into main
CI / Core-Modul (Build/Vet/Test) (push) Successful in 38s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 2s
CI / Web (pdf-viewer) (push) Failing after 40s
CI / Web (dms-browser) (push) Failing after 54s
CI / Web (shl) (push) Failing after 8s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 14s
Supply-Chain-Scan (Go) / govulncheck (push) Failing after 43s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Failing after 29s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 3s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 3s
2026-10-30 00:24:20 +01:00
patrickandClaude Sonnet 5.5 976878136f Merge main in feat/connect-datev-registrieren
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Successful in 19m25s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Failing after 2s
CI / Core-Modul (Build/Vet/Test) (push) Successful in 44s
CI / Core-Modul (Build/Vet/Test) (pull_request) Successful in 44s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Successful in 3s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 2s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Failing after 14s
DB-Integrationstests / db-tests (pull_request) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 3s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Failing after 44s
CI / Web (dms-browser) (pull_request) Failing after 55s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Successful in 19m26s
CI / Web (shl) (push) Failing after 9s
CI / Web (dms-browser) (push) Failing after 1m1s
CI / Web (pdf-viewer) (push) Failing after 37s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Successful in 2s
CI / Web (pdf-viewer) (pull_request) Failing after 41s
CI / Web (shl) (pull_request) Failing after 8s
Supply-Chain-Scan (Go) / govulncheck (push) Failing after 46s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Successful in 18m53s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Failing after 1m20s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 4s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 14s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-30 00:24:14 +01:00
patrick dc8f6476ab Merge pull request 'connect/statusapi: Rolle aus dem Token an die Policy' (#428) from fix/statusapi-rolle into main
CI / Web (shl) (push) Failing after 9s
CI / Web (pdf-viewer) (push) Failing after 50s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 4s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 4s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 14s
CI / Web (dms-browser) (push) Failing after 1m1s
Supply-Chain-Scan (Go) / govulncheck (push) Failing after 50s
CI / Core-Modul (Build/Vet/Test) (push) Successful in 49s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 2s
2026-10-30 00:23:50 +01:00
patrick bc95515d98 Merge pull request 'workflow, connect: bearerauth aus common' (#427) from refactor/bearerauth-common into main
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 3s
common Versions-Tag / tag (push) Successful in 19m12s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 3s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 15s
CI / Web (dms-browser) (push) Failing after 52s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 2s
CI / Web (pdf-viewer) (push) Failing after 35s
CI / Web (shl) (push) Failing after 7s
CI / Core-Modul (Build/Vet/Test) (push) Successful in 42s
Supply-Chain-Scan (Go) / govulncheck (push) Failing after 42s
2026-10-30 00:23:48 +01:00
patrickandClaude Sonnet 5.5 753af35888 connect: DATEV-Export in connect-app registriert, mit Rechtepruefung
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Web (dms-browser) (push) Failing after 51s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Failing after 14s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Successful in 18m46s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Successful in 18m59s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 2s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 3s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Successful in 4s
CI / Web (dms-browser) (pull_request) Failing after 58s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 15s
CI / Web (shl) (pull_request) Failing after 9s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Failing after 42s
Supply-Chain-Scan (Go) / govulncheck (push) Failing after 43s
CI / Web (pdf-viewer) (pull_request) Failing after 43s
CI / Web (pdf-viewer) (push) Failing after 36s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Successful in 18m38s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Failing after 2s
DB-Integrationstests / db-tests (pull_request) Canceled after 0s
CI / Web (shl) (push) Failing after 7s
CI / Core-Modul (Build/Vet/Test) (push) Successful in 39s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (pull_request) Successful in 46s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 3s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Failing after 1m16s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Successful in 3s
/datev/exports braucht connect.datev.write (POST) bzw. connect.datev.read
(GET) mit der Rolle aus X-User-Role. Registriert nur mit
NEXARCH_CONNECT_APP_ARCHIVE_URL; die DATEV-Uebertragung bleibt aus.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-30 00:23:17 +01:00
patrickandClaude Sonnet 5.5 422e96cc20 connect/statusapi: Rolle aus X-User-Role an die Policy
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Successful in 2s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Successful in 18m56s
DB-Integrationstests / db-tests (pull_request) Canceled after 0s
CI / Web (pdf-viewer) (push) Failing after 40s
CI / Web (pdf-viewer) (pull_request) Failing after 38s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 14s
Supply-Chain-Scan (Go) / govulncheck (push) Failing after 41s
CI / Core-Modul (Build/Vet/Test) (pull_request) Successful in 42s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Failing after 36s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Failing after 29s
CI / Web (dms-browser) (pull_request) Failing after 50s
CI / Web (dms-browser) (push) Failing after 57s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Successful in 18m52s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 5s
CI / Core-Modul (Build/Vet/Test) (push) Successful in 40s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 3s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 3s
CI / Web (shl) (push) Failing after 8s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Failing after 1m17s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Successful in 18m24s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Web (shl) (pull_request) Failing after 9s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Successful in 2s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Failing after 14s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Failing after 3s
Bisher fragte statusapi mit fester Rolle user. bearerauth setzt X-User-Role
aus dem Token; ohne Header bleibt es bei user.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-29 23:42:16 +01:00
patrickandClaude Sonnet 5.5 df9581cf45 workflow, connect: bearerauth aus common/bearerauth
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Failing after 29s
CI / Web (dms-browser) (push) Failing after 52s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Successful in 18m45s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Failing after 38s
CI / Core-Modul (Build/Vet/Test) (pull_request) Successful in 39s
CI / Web (dms-browser) (pull_request) Failing after 55s
CI / Web (shl) (push) Failing after 8s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Successful in 18m28s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 4s
CI / Core-Modul (Build/Vet/Test) (push) Successful in 38s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Failing after 1m13s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Failing after 3s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Successful in 2s
DB-Integrationstests / db-tests (pull_request) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 3s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 2s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Successful in 18m16s
Workflow-Pflichttest-Gate / pflichttest-gate (pull_request) Successful in 18m10s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Failing after 14s
common Pflichttest-Gate / pflichttest-gate (pull_request) Successful in 19m7s
Supply-Chain-Scan (Go) / govulncheck (push) Failing after 1m1s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 12s
CI / Web (pdf-viewer) (pull_request) Failing after 39s
CI / Web (shl) (pull_request) Failing after 7s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Successful in 2s
CI / Web (pdf-viewer) (push) Failing after 37s
Die beiden gleichen Kopien entfallen; common v0.2.2. Das Testpflicht-Muster
fuer bearerauth wandert vom workflow-Gate in das common-Gate.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-29 23:41:06 +01:00
patrick 1da0985ee9 Merge pull request 'docs: Arbeitskarten Portal-Betrieb 131' (#425) from docs/arbeitskarten-131 into main
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Failing after 29s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 13s
CI / Web (pdf-viewer) (push) Failing after 39s
CI / Web (shl) (push) Failing after 7s
CI / Core-Modul (Build/Vet/Test) (push) Successful in 39s
CI / Web (dms-browser) (push) Failing after 51s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 3s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 2s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 3s
2026-10-29 15:48:44 +01:00
patrick 550b8fad37 Merge pull request 'common: bearerauth aus workflow und connect' (#426) from feat/common-bearerauth into main
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 4s
CI / Core-Modul (Build/Vet/Test) (push) Failing after 1m1s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 25s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 4s
common Versions-Tag / tag (push) Successful in 17m49s
CI / Web (pdf-viewer) (push) Failing after 49s
CI / Web (dms-browser) (push) Failing after 1m25s
CI / Web (shl) (push) Failing after 7s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 2s
Supply-Chain-Scan (Go) / govulncheck (push) Failing after 49s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
2026-10-29 15:48:42 +01:00
patrickandClaude Sonnet 5.5 fb191e6a7e common: bearerauth aus workflow und connect
Supply-Chain-Scan (Go) / govulncheck (pull_request) Canceled after 0s
CI / Web (dms-browser) (pull_request) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (push) Canceled after 0s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Canceled after 0s
CI / Web (shl) (pull_request) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Canceled after 0s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Canceled after 0s
common Pflichttest-Gate / pflichttest-gate (pull_request) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (pull_request) Canceled after 0s
CI / Web (pdf-viewer) (pull_request) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Canceled after 0s
CI / Web (pdf-viewer) (push) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
CI / Web (dms-browser) (push) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Canceled after 0s
CI / Web (shl) (push) Canceled after 0s
DB-Integrationstests / db-tests (pull_request) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (push) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Canceled after 0s
Beide Module halten dieselbe Middleware (nur der Kommentar unterscheidet
sich). Hier liegt die gemeinsame Fassung; workflow und connect stellen
nach dem Tag in einem Folge-PR um.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-29 15:41:41 +01:00
patrickandClaude Sonnet 5.5 8db62327dc docs: Arbeitskarten D-02, D-03 aktualisiert, D-08 und D-09 ergaenzt
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Canceled after 0s
CI / Web (shl) (push) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (pull_request) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Canceled after 0s
CI / Web (shl) (pull_request) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Canceled after 0s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Canceled after 0s
CI / Web (pdf-viewer) (pull_request) Canceled after 0s
CI / Web (pdf-viewer) (push) Canceled after 0s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Canceled after 0s
CI / Web (dms-browser) (pull_request) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (push) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Canceled after 0s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Canceled after 0s
CI / Web (dms-browser) (push) Canceled after 0s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-29 15:37:03 +01:00
patrick 52a6a4bfd7 Merge pull request 'deploy: Skript zum Einrichten des Portals auf 131' (#424) from feat/skript-131-portal into main
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 19s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 3s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 4s
CI / Core-Modul (Build/Vet/Test) (push) Failing after 1m5s
CI / Web (shl) (push) Failing after 12s
CI / Web (dms-browser) (push) Failing after 1m27s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 2s
CI / Web (pdf-viewer) (push) Failing after 1m5s
2026-10-29 11:17:23 +01:00
patrickandClaude Sonnet 5.5 d7172a1699 deploy: npm ci bei abweichender Lockdatei, shl nutzt node_modules der App
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Canceled after 0s
CI / Web (shl) (pull_request) Canceled after 0s
CI / Web (pdf-viewer) (push) Canceled after 0s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (push) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Canceled after 0s
CI / Web (dms-browser) (pull_request) Canceled after 0s
CI / Web (pdf-viewer) (pull_request) Canceled after 0s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Canceled after 0s
CI / Web (shl) (push) Canceled after 0s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Canceled after 0s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (pull_request) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
CI / Web (dms-browser) (push) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Canceled after 0s
Account auf 131 hat Next 14.2.35, die Quelle braucht 16.3.5 (--webpack
unbekannt). Abhaengigkeiten werden jetzt per Lockdatei-Hash erneuert,
shl/node_modules wird auf die der App verlinkt (eine React-Instanz).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-29 10:58:13 +01:00
patrickandClaude Sonnet 5.5 154f3fc505 deploy: shl bekommt node_modules-Link, sonst findet der Portal-Build react nicht
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (push) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (pull_request) Canceled after 0s
CI / Web (shl) (pull_request) Canceled after 0s
CI / Web (dms-browser) (push) Canceled after 0s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Canceled after 0s
CI / Web (pdf-viewer) (push) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Canceled after 0s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Canceled after 0s
CI / Web (dms-browser) (pull_request) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Canceled after 0s
CI / Web (shl) (push) Canceled after 0s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Canceled after 0s
CI / Web (pdf-viewer) (pull_request) Canceled after 0s
Auf 131 hat /opt/nexarch-web/portal/web/shl kein node_modules; der rsync
laesst node_modules aus. Typpruefung scheiterte mit TS2307 'react'.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-29 10:55:30 +01:00
patrickandClaude Sonnet 5.5 638150450d deploy: Skript zum Einrichten des Portals auf 131
Supply-Chain-Scan (Go) / govulncheck (pull_request) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Canceled after 0s
CI / Web (dms-browser) (pull_request) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (push) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Canceled after 0s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Canceled after 0s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (pull_request) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Canceled after 0s
CI / Web (dms-browser) (push) Canceled after 0s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Canceled after 0s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Canceled after 0s
CI / Web (pdf-viewer) (push) Canceled after 0s
CI / Web (pdf-viewer) (pull_request) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
CI / Web (shl) (push) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Canceled after 0s
CI / Web (shl) (pull_request) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
Env-Datei, Unit, nginx-Block 8450, nft-Regel, Audience portal, CORS am Core
und Neubau von Portal und Login in einem idempotenten Lauf mit Sicherungen.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-29 00:00:29 +01:00
patrick 21e2e34940 Merge pull request 'portal: Betriebszustand an den Modul-Kacheln' (#423) from feat/portal-kachel-status into main
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 2s
CI / Web (shl) (push) Failing after 11s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 21s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 3s
CI / Web (dms-browser) (push) Failing after 1m29s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 4s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Web (pdf-viewer) (push) Failing after 1m4s
CI / Core-Modul (Build/Vet/Test) (push) Successful in 1m11s
2026-10-28 23:57:41 +01:00
patrickandClaude Sonnet 5.5 63dc5c5a6f portal: Betriebszustand an den Modul-Kacheln
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (pull_request) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Canceled after 0s
CI / Web (shl) (push) Canceled after 0s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Canceled after 0s
CI / Web (dms-browser) (push) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Canceled after 0s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Canceled after 0s
CI / Web (pdf-viewer) (pull_request) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
CI / Web (shl) (pull_request) Canceled after 0s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (push) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Canceled after 0s
CI / Web (dms-browser) (pull_request) Canceled after 0s
CI / Web (pdf-viewer) (push) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Canceled after 0s
Die Kachel zeigt Punkt und Text, wenn die Statusseite ein Pruefziel kennt,
dessen Name dem Modul-Schluessel oder der Bezeichnung entspricht. Ohne
Berechtigung (403) oder ohne Statusquelle bleibt die Kachel unveraendert.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 23:56:02 +01:00
patrick 44c0919724 Merge pull request 'account: nach der Anmeldung direkt ins Portal' (#422) from feat/login-zum-portal into main
CI / Core-Modul (Build/Vet/Test) (push) Successful in 1m4s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 18s
CI / Web (pdf-viewer) (push) Failing after 1m3s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 5s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 2s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 5s
CI / Web (shl) (push) Failing after 14s
CI / Web (dms-browser) (push) Failing after 1m23s
2026-10-28 23:49:57 +01:00
patrickandClaude Sonnet 5.5 e5e5a5a4ed account: nach der Anmeldung direkt ins Portal, gueltige Sitzung ueberspringt das Formular
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Canceled after 0s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Canceled after 0s
CI / Web (shl) (pull_request) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
CI / Web (dms-browser) (push) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (pull_request) Canceled after 0s
CI / Web (pdf-viewer) (pull_request) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (push) Canceled after 0s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Canceled after 0s
CI / Web (pdf-viewer) (push) Canceled after 0s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Canceled after 0s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Canceled after 0s
CI / Web (shl) (push) Canceled after 0s
CI / Web (dms-browser) (pull_request) Canceled after 0s
NEXT_PUBLIC_PORTAL_URL ist das Ziel (ohne Wert wie bisher /profile). Das
Profil bekommt einen Link zum Portal.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 23:47:00 +01:00
patrick 303c5cfcae Merge pull request 'docs: Arbeitskarten D-05 und D-07' (#421) from docs/arbeitskarten-d07 into main
CI / Core-Modul (Build/Vet/Test) (push) Successful in 1m10s
CI / Web (dms-browser) (push) Failing after 1m26s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 6s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 2s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 24s
CI / Web (pdf-viewer) (push) Failing after 1m1s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 6s
CI / Web (shl) (push) Failing after 13s
2026-10-28 23:31:55 +01:00
patrickandClaude Sonnet 5.5 81ecfe8d86 docs: Arbeitskarten D-05 aktualisiert, D-07 (Issue 369) neu
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Canceled after 0s
CI / Web (shl) (pull_request) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (pull_request) Canceled after 0s
CI / Web (dms-browser) (push) Canceled after 0s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (push) Canceled after 0s
CI / Web (pdf-viewer) (pull_request) Canceled after 0s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Canceled after 0s
CI / Web (pdf-viewer) (push) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Canceled after 0s
CI / Web (dms-browser) (pull_request) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Canceled after 0s
CI / Web (shl) (push) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Canceled after 0s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 23:30:54 +01:00
patrick 9bb138166d Merge pull request 'connect: Bearer-Pruefung in connect-app und Frontends' (#419) from feat/connect-bearer into main
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (push) Successful in 1m6s
Supply-Chain-Scan (Go) / govulncheck (push) Failing after 1m12s
CI / Web (dms-browser) (push) Failing after 1m26s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 20s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 6s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 5s
CI / Web (pdf-viewer) (push) Failing after 1m1s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 3s
CI / Web (shl) (push) Failing after 15s
2026-10-28 23:30:24 +01:00
patrickandClaude Sonnet 5.5 1de1da433f Merge main in feat/connect-bearer
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Successful in 26m10s
Supply-Chain-Scan (Go) / govulncheck (push) Failing after 1m0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (push) Successful in 1m0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Web (shl) (push) Failing after 17s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Successful in 26m46s
CI / Web (pdf-viewer) (pull_request) Failing after 1m6s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Successful in 4s
CI / Web (shl) (pull_request) Failing after 11s
CI / Core-Modul (Build/Vet/Test) (pull_request) Successful in 1m3s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Failing after 1m51s
CI / Web (pdf-viewer) (push) Failing after 1m13s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Failing after 18s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 28s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Successful in 4s
CI / Web (dms-browser) (push) Failing after 1m27s
CI / Web (dms-browser) (pull_request) Failing after 1m38s
DB-Integrationstests / db-tests (pull_request) Canceled after 0s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Successful in 25m41s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 3s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Failing after 51s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 4s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 23:30:01 +01:00
patrick 1b095507f3 Merge pull request 'RBAC #369: Rolle aus dem Token, Policy-Pruefung fail-closed' (#420) from fix/rbac-role-failclosed into main
CI / Web (pdf-viewer) (push) Failing after 1m3s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 3s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 2s
CI / Core-Modul (Build/Vet/Test) (push) Successful in 1m7s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 4s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Web (shl) (push) Failing after 12s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 24s
CI / Web (dms-browser) (push) Failing after 1m26s
Supply-Chain-Scan (Go) / govulncheck (push) Failing after 55s
2026-10-28 23:29:22 +01:00
patrick a2832c5b53 Merge pull request 'SEC: RBAC-Pruefung in ai-search, workflow-tasks, connect-setup (#369)' (#418) from fix/issue-369-rbac into main
CI / Web (shl) (push) Failing after 16s
CI / Core-Modul (Build/Vet/Test) (push) Successful in 1m6s
CI / Web (pdf-viewer) (push) Failing after 1m0s
Supply-Chain-Scan (Go) / govulncheck (push) Failing after 1m17s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 3s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 4s
CI / Web (dms-browser) (push) Failing after 1m27s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 5s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 20s
2026-10-28 23:29:04 +01:00
patrick 87bed4d213 Merge pull request 'shl: gemeinsamer Bearer-Helfer fuer Frontends' (#417) from refactor/shl-bearer into main
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 3s
CI / Core-Modul (Build/Vet/Test) (push) Successful in 1m0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 4s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 3s
CI / Web (shl) (push) Failing after 13s
CI / Web (pdf-viewer) (push) Failing after 1m4s
CI / Web (dms-browser) (push) Failing after 1m22s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 26s
2026-10-28 23:29:02 +01:00
patrickandClaude Sonnet 5.5 bfbb375649 connect bearerauth: X-User-Role nur aus dem Token
CI / Web (pdf-viewer) (push) Failing after 1m3s
CI / Core-Modul (Build/Vet/Test) (push) Successful in 1m16s
CI / Web (pdf-viewer) (pull_request) Failing after 59s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Failing after 1m51s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Successful in 25m50s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 4s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Failing after 22s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Successful in 4s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 21s
CI / Core-Modul (Build/Vet/Test) (pull_request) Successful in 1m4s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Successful in 24m33s
CI / Web (dms-browser) (pull_request) Failing after 1m39s
CI / Web (shl) (push) Failing after 11s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 5s
CI / Web (dms-browser) (push) Failing after 1m27s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Successful in 4s
CI / Web (shl) (pull_request) Failing after 12s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Failing after 52s
DB-Integrationstests / db-tests (pull_request) Canceled after 0s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Successful in 24m39s
Supply-Chain-Scan (Go) / govulncheck (push) Failing after 1m1s
Gleiche Aenderung wie workflow/internal/bearerauth, damit die Rechtepruefung
aus #418 die echte Rolle bekommt.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 23:09:17 +01:00
patrickandClaude Sonnet 5.5 698f0490f2 RBAC #369: Rolle aus dem Token durchreichen, Policy-Pruefung fail-closed
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 4s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Successful in 5s
CI / Web (shl) (pull_request) Failing after 13s
Workflow-Pflichttest-Gate / pflichttest-gate (pull_request) Successful in 24m45s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Successful in 4s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 21s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Failing after 25s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Failing after 1m52s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Successful in 24m26s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Failing after 1m2s
Supply-Chain-Scan (Go) / govulncheck (push) Failing after 1m2s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 4s
CI / Web (shl) (push) Failing after 16s
CI / Web (pdf-viewer) (push) Failing after 1m2s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Successful in 24m43s
CI / Core-Modul (Build/Vet/Test) (push) Successful in 1m6s
CI / Web (pdf-viewer) (pull_request) Failing after 55s
AI-Pflichttest-Gate / pflichttest-gate (pull_request) Successful in 24m42s
CI / Web (dms-browser) (pull_request) Failing after 1m32s
DB-Integrationstests / db-tests (pull_request) Canceled after 0s
CI / Web (dms-browser) (push) Failing after 1m23s
CI / Core-Modul (Build/Vet/Test) (pull_request) Successful in 1m0s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Successful in 25m10s
workflow bearerauth setzt X-User-Role nur aus den Claims (Client-Header werden
entfernt); taskapi und connectorsetupapi fragen mit dieser Rolle, Default user.
Migration 0029: connect.setup.write nur fuer tenant_admin.
ai-app, workflow-app, connect-app starten ohne POLICY_URL nicht mehr, ausser
mit NEXARCH_<MODUL>_APP_POLICY_DISABLED=1.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 23:08:57 +01:00
patrickandClaude Sonnet 5.5 3f83038203 connect: Bearer-Pruefung in connect-app, Frontends connect-*, datev-export, dms2-import per Sitzung
CI / Web (shl) (push) Failing after 8s
CI / Core-Modul (Build/Vet/Test) (push) Successful in 40s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Successful in 22m20s
CI / Web (dms-browser) (pull_request) Failing after 55s
DB-Integrationstests / db-tests (pull_request) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 2s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Failing after 1m54s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
CI / Web (pdf-viewer) (pull_request) Failing after 38s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Successful in 2s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Failing after 16s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 14s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Successful in 3s
CI / Web (dms-browser) (push) Failing after 49s
CI / Web (pdf-viewer) (push) Failing after 37s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 3s
CI / Web (shl) (pull_request) Failing after 8s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Successful in 23m46s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (push) Failing after 42s
CI / Core-Modul (Build/Vet/Test) (pull_request) Successful in 41s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Failing after 1m3s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Successful in 23m56s
connect/internal/bearerauth (wie workflow): verifiziertes Core-Token setzt
X-User-Id/X-Tenant-Slug, ohne Token nur mit NEXARCH_CONNECT_APP_ALLOW_HEADER_AUTH=1.
Mit JWKS ist NEXARCH_CONNECT_APP_TENANT_SLUG Pflicht (Mandant der Datenbank).
common auf v0.2.1 (moduleauth). Frontends nutzen createBearerClient("connect").

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 22:44:50 +01:00
patrickandClaude Sonnet 5.5 0a4c0aaf92 SEC: RBAC-Pruefung in ai-search, workflow-tasks, connect-setup (#369)
CI / Core-Modul (Build/Vet/Test) (pull_request) Successful in 37s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Failing after 13s
AI-Pflichttest-Gate / pflichttest-gate (pull_request) Successful in 15m54s
CI / Core-Modul (Build/Vet/Test) (push) Successful in 38s
Workflow-Pflichttest-Gate / pflichttest-gate (pull_request) Failing after 22m6s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Successful in 15m49s
CI / Web (pdf-viewer) (pull_request) Failing after 38s
CI / Web (shl) (pull_request) Failing after 8s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Successful in 16m2s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Successful in 2s
CI / Web (pdf-viewer) (push) Failing after 38s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (push) Failing after 37s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Failing after 56s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Successful in 16m18s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 3s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Successful in 2s
DB-Integrationstests / db-tests (pull_request) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
CI / Web (dms-browser) (push) Failing after 53s
CI / Web (dms-browser) (pull_request) Failing after 53s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 3s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 14s
CI / Web (shl) (push) Failing after 8s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Failing after 1m13s
- semanticsearchapi: ai.search.read per policyclient (WithPolicy), Tenant aus
  dem Anfragekoerper, nach Service-Token-Pruefung
- taskapi: workflow.task.read (GET /tasks) und workflow.task.write (PATCH
  status); Paket jetzt in cmd/workflow-app verdrahtet (lag bisher nur im
  a11y-Fixture) und damit hinter bearerauth
- connectorsetupapi: connect.setup.read/write je Route
- Migration 0029 (up/down): neue Rechte fuer user und tenant_admin
- Wiring: NEXARCH_{AI,WORKFLOW,CONNECT}_APP_POLICY_URL/_POLICY_SERVICE_TOKEN;
  ohne URL Warnung und keine Pruefung (Uebergang), URL ohne Token Startfehler;
  connect-app setzt die bestehende statusapi-Pruefung damit ebenfalls scharf
- Tests je API: 403 bei Ablehnung, 200 bei Freigabe, 503 bei Ausfall, Policy
  wird ohne Identitaet/Service-Token nicht befragt
- MODULE_REGISTRY: ai_search_chat, workflow_tasks, connect_setup (+ Icons,
  Portal-Rechteliste)
- Doku: BETRIEB-PORTAL.md, ENG-11 Umgebungsvariablen

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 22:36:43 +01:00
patrickandClaude Sonnet 5.5 e2d941287f shl: createBearerClient und bearerHeader statt vier kopierter http/forward-Dateien
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 3s
CI / Web (shl) (push) Failing after 8s
CI / Core-Modul (Build/Vet/Test) (pull_request) Successful in 36s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Successful in 15m45s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 13s
CI / Core-Modul (Build/Vet/Test) (push) Successful in 40s
CI / Web (dms-browser) (pull_request) Failing after 47s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Successful in 3s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Failing after 12s
CI / Web (shl) (pull_request) Failing after 9s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Failing after 51s
CI / Web (dms-browser) (push) Failing after 53s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Failing after 1m16s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Successful in 15m39s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Successful in 2s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
CI / Web (pdf-viewer) (push) Failing after 40s
CI / Web (pdf-viewer) (pull_request) Failing after 40s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Successful in 15m22s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 3s
workflow-approvals, workflow-tasks, workflow-cases und routing-rules hatten
identische lib/http.ts und lib/forward.ts; mail-search eine fuenfte Variante.
Verhalten unveraendert.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 22:22:37 +01:00
patrick ddda4b93a7 Merge pull request 'mail-search: Akte-Kontext per Bearer' (#416) from feat/mailsearch-bearer into main
CI / Web (dms-browser) (push) Failing after 48s
CI / Web (pdf-viewer) (push) Failing after 38s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 2s
CI / Core-Modul (Build/Vet/Test) (push) Successful in 39s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 13s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 2s
CI / Web (shl) (push) Failing after 8s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 5s
2026-10-28 22:16:00 +01:00
patrick 237403e27d Merge pull request 'workflow: Pflichttest-Gate fuer bearerauth' (#415) from feat/workflow-gate-bearerauth into main
Supply-Chain-Scan (Go) / govulncheck (push) Failing after 51s
CI / Web (dms-browser) (push) Failing after 54s
CI / Web (shl) (push) Failing after 8s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 4s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (push) Successful in 38s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 4s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 2s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 14s
CI / Web (pdf-viewer) (push) Failing after 39s
2026-10-28 22:15:58 +01:00
patrickandClaude Sonnet 5.5 db11334b21 mail-search: Akte-Kontext per Bearer (Audience workflow)
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (push) Failing after 36s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 3s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Successful in 3s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Failing after 1m20s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 14s
CI / Web (dms-browser) (push) Failing after 59s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 2s
CI / Web (pdf-viewer) (pull_request) Failing after 35s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Successful in 14m57s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Successful in 16m5s
CI / Web (pdf-viewer) (push) Failing after 42s
CI / Web (shl) (pull_request) Failing after 7s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Successful in 16m44s
CI / Web (shl) (push) Failing after 8s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Failing after 12s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (pull_request) Successful in 39s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Failing after 48s
CI / Web (dms-browser) (pull_request) Failing after 49s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Successful in 3s
Die BFF-Route reicht mit Bearer nur das Token weiter; user/tenant aus der
URL gelten nur noch ohne Token. Der Client sendet das Token ueber eine
zweite Sitzung (module workflow), sobald NEXT_PUBLIC_CORE_API_BASE gesetzt ist.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 22:07:18 +01:00
patrickandClaude Sonnet 5.5 9bfe2e891b workflow: Pflichttest-Gate deckt internal/bearerauth ab
CI / Web (shl) (pull_request) Failing after 8s
CI / Web (dms-browser) (push) Failing after 50s
Supply-Chain-Scan (Go) / govulncheck (push) Failing after 51s
CI / Web (pdf-viewer) (push) Failing after 38s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Canceled after 0s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Successful in 14m55s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 3s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 3s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Failing after 12s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 13s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Failing after 28s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Successful in 14m44s
Workflow-Pflichttest-Gate / pflichttest-gate (pull_request) Successful in 14m49s
CI / Web (pdf-viewer) (pull_request) Failing after 39s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Failing after 1m12s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Successful in 14m59s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (push) Successful in 39s
CI / Web (shl) (push) Failing after 7s
CI / Core-Modul (Build/Vet/Test) (pull_request) Successful in 36s
CI / Web (dms-browser) (pull_request) Failing after 55s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Failing after 27s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Successful in 2s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Successful in 3s
Aenderungen an bearerauth (Durchsetzungspunkt fuer Bearer-Token der Frontends)
brauchen jetzt eine begleitende _test.go im selben Package.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 17:33:10 +01:00
patrick 57b3e2125e Merge pull request 'docs: Workflow-Demo und Bearer-Betrieb auf 204' (#414) from docs/betrieb-demo-bearer into main
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Web (pdf-viewer) (push) Failing after 39s
CI / Web (dms-browser) (push) Failing after 54s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 2s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 13s
CI / Web (shl) (push) Failing after 7s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 4s
CI / Core-Modul (Build/Vet/Test) (push) Successful in 36s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 2s
2026-10-28 17:32:27 +01:00
patrickandClaude Sonnet 5.5 dbf0877789 docs: Workflow-Demo und Bearer-Betrieb auf 204
CI / Core-Modul (Build/Vet/Test) (pull_request) Successful in 40s
CI / Web (pdf-viewer) (push) Failing after 40s
CI / Web (pdf-viewer) (pull_request) Failing after 37s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 2s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 12s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Successful in 14m20s
CI / Web (dms-browser) (push) Failing after 49s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Successful in 5s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Failing after 46s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (push) Successful in 37s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Successful in 4s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 2s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Failing after 1m8s
CI / Web (shl) (push) Failing after 9s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Successful in 14m20s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Successful in 14m28s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Failing after 13s
CI / Web (dms-browser) (pull_request) Failing after 49s
CI / Web (shl) (pull_request) Failing after 8s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 16:12:40 +01:00
patrick 701fb4e2c4 Merge pull request 'web: Modul-Tokens mit explizitem Zielmodul (NEXT_PUBLIC_TOKEN_MODULE)' (#413) from feat/frontends-token-module into main
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 14s
CI / Web (pdf-viewer) (push) Failing after 36s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 3s
CI / Core-Modul (Build/Vet/Test) (push) Successful in 40s
CI / Web (dms-browser) (push) Failing after 52s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 4s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 4s
CI / Web (shl) (push) Failing after 7s
2026-10-28 16:05:23 +01:00
patrickandClaude Sonnet 5.5 c5474111f9 web: Modul-Tokens mit explizitem Zielmodul anfordern (NEXT_PUBLIC_TOKEN_MODULE, Standard dms)
CI / Web (dms-browser) (push) Failing after 52s
CI / Web (pdf-viewer) (push) Failing after 35s
CI / Core-Modul (Build/Vet/Test) (push) Successful in 40s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 3s
CI / Web (shl) (pull_request) Failing after 8s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Successful in 3s
CI / Web (pdf-viewer) (pull_request) Failing after 40s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Failing after 12s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Canceled after 0s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Successful in 14m22s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 2s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Successful in 14m2s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Successful in 14m26s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Web (shl) (push) Failing after 8s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Successful in 4s
CI / Core-Modul (Build/Vet/Test) (pull_request) Successful in 37s
CI / Web (dms-browser) (pull_request) Failing after 48s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Failing after 49s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 13s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Failing after 1m15s
Vorbereitung auf mehrere NEXARCH_ACCOUNT_MODULE_AUDIENCES: der Core lehnt
Token-Anfragen ohne ?module ab, sobald mehr als eine Audience konfiguriert ist.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 15:59:16 +01:00
patrick 21838e87d0 Merge pull request 'Frontends auf Bearer: Portal-BFF, workflow-approvals, workflow-tasks' (#412) from feat/frontends-bearer into main
CI / Web (dms-browser) (push) Failing after 49s
CI / Web (pdf-viewer) (push) Failing after 38s
CI / Web (shl) (push) Failing after 9s
CI / Core-Modul (Build/Vet/Test) (push) Successful in 36s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 3s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 12s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 3s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 2s
2026-10-28 15:05:38 +01:00
patrickandClaude Sonnet 5.5 77b46945f2 workflow-cases, routing-rules: Sitzung und Bearer statt X-User-Id aus der URL
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Successful in 13m50s
CI / Web (dms-browser) (push) Failing after 54s
CI / Web (shl) (push) Failing after 8s
CI / Web (pdf-viewer) (push) Failing after 37s
CI / Web (dms-browser) (pull_request) Failing after 47s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Successful in 2s
CI / Web (pdf-viewer) (pull_request) Failing after 36s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Failing after 47s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Successful in 2s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Successful in 13m33s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Successful in 13m40s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 14s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Failing after 12s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 4s
CI / Core-Modul (Build/Vet/Test) (pull_request) Successful in 36s
CI / Web (shl) (pull_request) Failing after 7s
CI / Core-Modul (Build/Vet/Test) (push) Successful in 40s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 3s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Failing after 1m19s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 15:05:34 +01:00
patrickandClaude Sonnet 5.5 0032aa7d81 workflow-approvals, workflow-tasks: Sitzung und Bearer statt X-User-Id aus der URL
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
CI / Web (dms-browser) (pull_request) Failing after 56s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Successful in 13m30s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Failing after 13s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Failing after 46s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 3s
CI / Web (pdf-viewer) (pull_request) Failing after 47s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 12s
CI / Web (pdf-viewer) (push) Failing after 35s
CI / Web (shl) (pull_request) Failing after 8s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Successful in 13m41s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Failing after 1m15s
CI / Web (shl) (push) Failing after 7s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Successful in 2s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Successful in 3s
CI / Core-Modul (Build/Vet/Test) (pull_request) Successful in 39s
CI / Core-Modul (Build/Vet/Test) (push) Successful in 37s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 2s
CI / Web (dms-browser) (push) Failing after 49s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Successful in 13m36s
Mit NEXT_PUBLIC_CORE_API_BASE holen die Frontends ein workflow-Token ueber die
Sitzung und senden Authorization; die BFF-Routen reichen es weiter und ignorieren
userId/tenant aus der URL. Ohne die Variable bleibt der Uebergangsweg.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 15:02:14 +01:00
patrickandClaude Sonnet 5.5 07b4ccbda5 portal: Workflow-Aufruf per Bearer (X-Workflow-Token), X-User-Id nur noch als Uebergang
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 14:59:43 +01:00
patrick 92dd8d33ca Merge pull request 'docs/deploy: Portal-Betrieb 131/204 aufnehmen' (#411) from docs/betrieb-portal into main
CI / Web (pdf-viewer) (push) Failing after 37s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 3s
CI / Web (shl) (push) Failing after 8s
CI / Core-Modul (Build/Vet/Test) (push) Successful in 40s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 3s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Web (dms-browser) (push) Failing after 52s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 13s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 3s
2026-10-28 14:55:50 +01:00
patrickandClaude Sonnet 5.5 6da280f91d docs/deploy: Portal-Betrieb 131/204, Unit-Vorlage, nginx-Proxy fuer 204
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 3s
CI / Web (pdf-viewer) (push) Failing after 38s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 4s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 14s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Successful in 13m12s
CI / Core-Modul (Build/Vet/Test) (push) Successful in 41s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Successful in 13m6s
CI / Web (shl) (push) Failing after 8s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Successful in 13m24s
CI / Web (dms-browser) (push) Failing after 53s
CI / Core-Modul (Build/Vet/Test) (pull_request) Successful in 42s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Successful in 3s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Failing after 12s
CI / Web (shl) (pull_request) Failing after 8s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Failing after 45s
CI / Web (pdf-viewer) (pull_request) Failing after 35s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Successful in 3s
CI / Web (dms-browser) (pull_request) Failing after 49s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Failing after 1m12s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Canceled after 0s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 14:53:31 +01:00
patrick a16d8cef66 Merge pull request 'account-api: DB-Start mit Backoff, Fehlerklassifizierung, Restart-Limits' (#410) from fix/account-api-start into main
CI / Web (shl) (push) Failing after 8s
CI / Web (dms-browser) (push) Failing after 57s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 13s
CI / Web (pdf-viewer) (push) Failing after 35s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (push) Failing after 30s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 2s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 3s
CI / Core-Modul (Build/Vet/Test) (push) Successful in 40s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 3s
2026-10-28 14:52:59 +01:00
patrick 6d3a4f8e56 Merge pull request 'Portal: moderne Oberflaeche - Kacheln mit Icons, Favoriten, Befehlsleiste, Zustaende' (#409) from feat/portal-ui-1 into main
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 3s
CI / Web (pdf-viewer) (push) Failing after 38s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 14s
CI / Core-Modul (Build/Vet/Test) (push) Successful in 38s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 3s
CI / Web (dms-browser) (push) Failing after 51s
CI / Web (shl) (push) Failing after 8s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 3s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
2026-10-28 14:52:56 +01:00
patrickandClaude Sonnet 5.5 e428de553d account-api: DB-Start mit Backoff und Fehlerklassifizierung, systemd-Restart-Limits
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Successful in 3s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 3s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 3s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Failing after 13s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Failing after 1m21s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Successful in 13m17s
CI / Web (shl) (push) Failing after 7s
Supply-Chain-Scan (Go) / govulncheck (push) Failing after 1m15s
CI / Web (pdf-viewer) (push) Failing after 38s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (pull_request) Successful in 37s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Successful in 13m17s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Failing after 53s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
CI / Web (pdf-viewer) (pull_request) Failing after 39s
CI / Web (shl) (pull_request) Failing after 7s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Successful in 2s
CI / Web (dms-browser) (push) Failing after 53s
CI / Core-Modul (Build/Vet/Test) (push) Successful in 39s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 14s
CI / Web (dms-browser) (pull_request) Failing after 49s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Successful in 12m42s
Dauerhafte Fehler (28P01, 28000, 3D000) beenden mit Exit 2 und klarer Meldung,
transiente Fehler werden bis NEXARCH_ACCOUNT_DB_STARTUP_TIMEOUT_SECONDS wiederholt.
Unit-Template: RestartSec, StartLimit, RestartPreventExitStatus=2.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 14:22:40 +01:00
patrickandClaude Sonnet 5.5 142d213178 portal: Befehlsleiste (Strg+K) mit Modulsprung und Dokumentsuche, Texte DE/EN, README
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Successful in 4s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Successful in 20m57s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Failing after 15m11s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Failing after 20s
CI / Web (pdf-viewer) (pull_request) Failing after 1m2s
CI / Core-Modul (Build/Vet/Test) (push) Successful in 1m37s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Successful in 3s
CI / Web (dms-browser) (pull_request) Failing after 1m31s
CI / Web (shl) (pull_request) Failing after 11s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 25s
CI / Web (pdf-viewer) (push) Failing after 1m19s
CI / Web (shl) (push) Failing after 14s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Failing after 4s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Successful in 14m31s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
CI / Web (dms-browser) (push) Failing after 2m3s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 6s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 5s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Failing after 1m28s
CI / Core-Modul (Build/Vet/Test) (pull_request) Successful in 1m1s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Successful in 12m53s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 2s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 12:32:12 +01:00
patrickandClaude Sonnet 5.5 2d582c5b08 portal: Favoriten, Zuletzt benutzt, einklappbare Gruppen, Reihenfolge nach Modulzahl
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 12:29:23 +01:00
patrickandClaude Sonnet 5.5 50b55a2c81 portal: visuelle Basis (Kacheln, Icons, Skeleton, Fehlerkarte), Tokens und Skip-Link-CSS in shl
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 12:26:57 +01:00
patrick e5b37b6278 Merge pull request 'workflow: Bearer-Token statt freier X-User-Id (B-06)' (#408) from feat/workflow-bearer into main
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 20s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 4s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 3s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 3s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Web (dms-browser) (push) Failing after 1m23s
Supply-Chain-Scan (Go) / govulncheck (push) Failing after 56s
CI / Web (pdf-viewer) (push) Failing after 1m7s
CI / Core-Modul (Build/Vet/Test) (push) Failing after 58s
CI / Web (shl) (push) Failing after 11s
2026-10-28 12:16:35 +01:00
patrick ae9dca26b0 Merge pull request 'mail: moduleauth aus common v0.2.1' (#407) from refactor/mail-moduleauth into main
CI / Web (dms-browser) (push) Failing after 1m21s
Supply-Chain-Scan (Go) / govulncheck (push) Failing after 57s
CI / Web (shl) (push) Failing after 12s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 2s
Mail-Schnittstellen-Vertragstests / contract-tests (push) Successful in 17m40s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 5s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 5s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 22s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Web (pdf-viewer) (push) Failing after 48s
CI / Core-Modul (Build/Vet/Test) (push) Successful in 1m0s
2026-10-28 12:16:29 +01:00
patrickandClaude Sonnet 5.5 0d07b79b04 workflow: Bearer-Token-Pruefung vor den APIs (Uebergangsschalter fuer X-User-Id)
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Successful in 17m44s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 2s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Failing after 2s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 4s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Failing after 33s
CI / Web (pdf-viewer) (push) Failing after 1m6s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Successful in 16m39s
Supply-Chain-Scan (Go) / govulncheck (push) Failing after 1m19s
CI / Web (pdf-viewer) (pull_request) Failing after 56s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 4s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Failing after 20s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Successful in 5s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (pull_request) Successful in 1m6s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 21s
CI / Core-Modul (Build/Vet/Test) (push) Successful in 59s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Successful in 5s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Successful in 17m11s
CI / Web (dms-browser) (pull_request) Failing after 1m27s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Failing after 1m2s
CI / Web (dms-browser) (push) Failing after 1m25s
CI / Web (shl) (push) Failing after 11s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Failing after 1m45s
CI / Web (shl) (pull_request) Failing after 14s
Workflow-Pflichttest-Gate / pflichttest-gate (pull_request) Successful in 17m29s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 11:38:57 +01:00
patrickandClaude Sonnet 5.5 8b9d0f8316 mail: moduleauth aus common v0.2.1
CI / Core-Modul (Build/Vet/Test) (pull_request) Successful in 1m0s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Failing after 1m43s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 2s
CI / Web (dms-browser) (push) Failing after 1m20s
CI / Web (dms-browser) (pull_request) Failing after 1m14s
CI / Web (pdf-viewer) (pull_request) Failing after 57s
Mail-Pflichttest-Gate / pflichttest-gate (pull_request) Successful in 16m43s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Successful in 17m22s
CI / Web (shl) (pull_request) Failing after 12s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Failing after 2s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 25s
CI / Web (pdf-viewer) (push) Failing after 59s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Successful in 4s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Failing after 22s
Supply-Chain-Scan (Go) / govulncheck (push) Failing after 56s
Mail-Schnittstellen-Vertragstests / contract-tests (push) Successful in 17m7s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Successful in 4s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Failing after 56s
Mail-Schnittstellen-Vertragstests / contract-tests (pull_request) Successful in 16m53s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Web (shl) (push) Failing after 11s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 6s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Successful in 5m49s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Successful in 17m20s
CI / Core-Modul (Build/Vet/Test) (push) Successful in 1m1s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 5s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 11:37:35 +01:00
patrick a9e2cfc29a Merge pull request 'workflow: migrate aus common v0.2.0' (#406) from refactor/workflow-migrate into main
CI / Web (dms-browser) (push) Failing after 1m21s
CI / Web (pdf-viewer) (push) Failing after 56s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 5s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 23s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 1s
CI / Web (shl) (push) Failing after 12s
CI / Core-Modul (Build/Vet/Test) (push) Successful in 1m5s
Supply-Chain-Scan (Go) / govulncheck (push) Failing after 53s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 4s
2026-10-28 11:34:05 +01:00
patrick c830edcede Merge pull request 'ai: migrate aus common v0.2.0' (#405) from refactor/ai-migrate into main
CI / Web (shl) (push) Failing after 11s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 5s
Supply-Chain-Scan (Go) / govulncheck (push) Failing after 58s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 5s
CI / Web (pdf-viewer) (push) Failing after 57s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 23s
CI / Core-Modul (Build/Vet/Test) (push) Successful in 1m4s
CI / Web (dms-browser) (push) Failing after 1m23s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 2s
2026-10-28 11:33:58 +01:00
patrick b0ec72b119 Merge pull request 'connect: migrate aus common v0.2.0' (#404) from refactor/connect-migrate into main
CI / Core-Modul (Build/Vet/Test) (push) Successful in 1m2s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 25s
CI / Web (pdf-viewer) (push) Failing after 1m0s
CI / Web (dms-browser) (push) Failing after 1m23s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 5s
Supply-Chain-Scan (Go) / govulncheck (push) Failing after 54s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 4s
CI / Web (shl) (push) Failing after 12s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 2s
2026-10-28 11:33:51 +01:00
patrick bf01806aa0 Merge pull request 'dms: migrate aus common v0.2.0' (#403) from refactor/dms-migrate into main
CI / Core-Modul (Build/Vet/Test) (push) Successful in 59s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 4s
Supply-Chain-Scan (Go) / govulncheck (push) Failing after 57s
CI / Web (shl) (push) Failing after 10s
CI / Web (pdf-viewer) (push) Failing after 59s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 22s
CI / Web (dms-browser) (push) Failing after 1m22s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 2s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 4s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
2026-10-28 11:33:45 +01:00
patrick e1fdc9e833 Merge pull request 'workflow: testdb aus common' (#393) from refactor/workflow-testdb into main
Workflow-Schnittstellen-Vertragstests / contract-tests (push) Successful in 16m10s
CI / Web (dms-browser) (push) Failing after 1m25s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 3s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 4s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 4s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 20s
CI / Web (pdf-viewer) (push) Failing after 55s
CI / Web (shl) (push) Failing after 14s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (push) Successful in 1m0s
Supply-Chain-Scan (Go) / govulncheck (push) Failing after 1m1s
2026-10-28 11:33:37 +01:00
patrick 3f3715ee08 Merge pull request 'connect: testdb aus common' (#392) from refactor/connect-testdb into main
CI / Core-Modul (Build/Vet/Test) (push) Failing after 57s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 4s
Connect-Schnittstellen-Vertragstests / contract-tests (push) Successful in 16m0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (push) Failing after 6m17s
CI / Web (pdf-viewer) (push) Failing after 59s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 2s
CI / Web (dms-browser) (push) Failing after 1m16s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 5s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 20s
CI / Web (shl) (push) Failing after 13s
2026-10-28 11:33:31 +01:00
patrick 28381ef9b0 Merge pull request 'ai: testdb aus common' (#391) from refactor/ai-testdb into main
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 4s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 21s
CI / Core-Modul (Build/Vet/Test) (push) Successful in 1m0s
CI / Web (shl) (push) Failing after 13s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 4s
AI-Schnittstellen-Vertragstests / contract-tests (push) Successful in 15m34s
Supply-Chain-Scan (Go) / govulncheck (push) Failing after 50s
CI / Web (pdf-viewer) (push) Failing after 1m3s
CI / Web (dms-browser) (push) Failing after 1m24s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 3s
2026-10-28 11:33:25 +01:00
patrick 4b6d2df36b Merge pull request 'dms: testdb aus common' (#390) from refactor/dms-testdb into main
CI / Web (dms-browser) (push) Failing after 1m20s
CI / Web (pdf-viewer) (push) Failing after 54s
Supply-Chain-Scan (Go) / govulncheck (push) Failing after 1m29s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 4s
CI / Web (shl) (push) Failing after 9s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 3s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 2s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 17s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (push) Successful in 59s
2026-10-28 11:33:17 +01:00
patrick c22ad6e8ca Merge pull request 'CI: Wegwerf-Postgres je Lauf fuer DB-Tests (A-08) und CODEOWNERS fuer common/ (A-09)' (#398) from feat/ci-db-cluster into main
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 4s
CI / Web (pdf-viewer) (push) Failing after 1m0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 17s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 3s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 2s
CI / Web (dms-browser) (push) Failing after 1m22s
CI / Web (shl) (push) Failing after 14s
CI / Core-Modul (Build/Vet/Test) (push) Successful in 58s
2026-10-28 11:32:56 +01:00
patrick ff79bf7514 Merge pull request 'portal: Zielmodul portal beim Token-Abruf (B-05)' (#397) from feat/portal-token-modul into main
CI / Web (pdf-viewer) (push) Failing after 57s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 6s
CI / Web (dms-browser) (push) Failing after 1m23s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 4s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Web (shl) (push) Failing after 13s
CI / Core-Modul (Build/Vet/Test) (push) Successful in 59s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 2s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 18s
2026-10-28 11:32:50 +01:00
patrick 75ef51e412 Merge pull request 'docs: Arbeitskarten Stand 2026-10-28' (#395) from docs/arbeitskarten-stand into main
CI / Web (pdf-viewer) (push) Failing after 55s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 4s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 18s
CI / Web (dms-browser) (push) Failing after 1m28s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 2s
CI / Core-Modul (Build/Vet/Test) (push) Successful in 57s
CI / Web (shl) (push) Failing after 14s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 4s
2026-10-28 11:32:45 +01:00
patrick d2d420c71e Merge pull request 'docs: Vorbildsysteme (Mail + DMS) in PRODUKTARCHITEKTUR, DEVLOGs' (#380) from docs/vorbildsysteme-devlogs into main
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 21s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 2s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 4s
CI / Web (shl) (push) Failing after 12s
CI / Core-Modul (Build/Vet/Test) (push) Failing after 1m2s
CI / Web (dms-browser) (push) Failing after 1m23s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 4s
CI / Web (pdf-viewer) (push) Failing after 54s
2026-10-28 11:32:40 +01:00
patrick 2d12a6ab98 Merge pull request 'fix(workflow): contracttest an moduleadapter.NewServer-Signatur angepasst' (#374) from fix/workflow-contracttest-verifier into main
CI / Web (dms-browser) (push) Failing after 1m21s
CI / Core-Modul (Build/Vet/Test) (push) Successful in 1m4s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 5s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 6s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 25s
CI / Web (pdf-viewer) (push) Failing after 57s
CI / Web (shl) (push) Failing after 11s
Workflow-Schnittstellen-Vertragstests / contract-tests (push) Successful in 15m5s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (push) Failing after 1m26s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 2s
2026-10-28 11:32:34 +01:00
patrick e0c0f7c537 Merge pull request 'refactor: Prüfgate-Pakete nach Funktion benannt, migration* gruppiert' (#373) from refactor/struktur-pakete into main
CI / Web (shl) (push) Failing after 14s
CI / Web (dms-browser) (push) Failing after 1m34s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 5s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 5s
CI / Core-Modul (Build/Vet/Test) (push) Successful in 59s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 21s
Supply-Chain-Scan (Go) / govulncheck (push) Failing after 1m11s
CI / Web (pdf-viewer) (push) Failing after 1m1s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 3s
2026-10-28 11:32:29 +01:00
patrick f5b8e7db7c Merge pull request 'mail: Vertragstest repariert, qa05/qa09-Gates skippen ohne .git/Root-Modul' (#400) from fix/mail-vertragstest into main
CI / Core-Modul (Build/Vet/Test) (push) Successful in 1m1s
CI / Web (dms-browser) (push) Failing after 1m29s
Mail-Schnittstellen-Vertragstests / contract-tests (push) Successful in 14m47s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 4s
Supply-Chain-Scan (Go) / govulncheck (push) Failing after 57s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 20s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Web (shl) (push) Failing after 13s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 2s
CI / Web (pdf-viewer) (push) Failing after 57s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 4s
2026-10-28 11:32:14 +01:00
patrick 50c32b6ef8 Merge pull request 'archtest: searchchatapi-a11y-fixture mit archtest:allow (behebt TestAllHTTPServersUseHTTPHarden)' (#399) from fix/root-archtest-reichweite into main
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 19s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 5s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (push) Successful in 1m1s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 2s
CI / Web (dms-browser) (push) Failing after 1m28s
CI / Web (shl) (push) Failing after 13s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 4s
CI / Web (pdf-viewer) (push) Failing after 58s
Supply-Chain-Scan (Go) / govulncheck (push) Failing after 1m0s
2026-10-28 11:32:09 +01:00
patrick 4d790fe925 Merge pull request 'dms/upload: MIME-Sniff gegen deklarierten Typ statt gegen Positivliste (Office/TIFF-Uploads)' (#401) from fix/dms-upload-sniff into main
Supply-Chain-Scan (Go) / govulncheck (push) Failing after 59s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 25s
CI / Web (shl) (push) Failing after 14s
CI / Core-Modul (Build/Vet/Test) (push) Failing after 1m21s
CI / Web (dms-browser) (push) Failing after 1m32s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 5s
CI / Web (pdf-viewer) (push) Failing after 56s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 6s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 3s
2026-10-28 11:32:03 +01:00
patrick a458ff6f95 Merge pull request 'dms: Audit-Log-Schreiben fuer nexarch_app-Login-Rollen (Migration 0036) + Test repariert' (#402) from fix/dms-audit-sequence-grant into main
CI / Web (dms-browser) (push) Failing after 1m21s
Supply-Chain-Scan (Go) / govulncheck (push) Failing after 1m3s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Web (pdf-viewer) (push) Failing after 1m45s
CI / Web (shl) (push) Failing after 16s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 4s
CI / Core-Modul (Build/Vet/Test) (push) Failing after 1m0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 2s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 4s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 22s
2026-10-28 11:31:57 +01:00
patrickandClaude Sonnet 5.5 642510bff2 workflow: migrate aus common v0.2.0 (Tabellenname bleibt unveraendert)
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Failing after 52s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Successful in 4s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Failing after 1m50s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 5s
CI / Web (shl) (push) Failing after 13s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Failing after 20s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Successful in 14m58s
CI / Core-Modul (Build/Vet/Test) (push) Failing after 1m0s
Supply-Chain-Scan (Go) / govulncheck (push) Failing after 1m18s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Failing after 2s
CI / Web (shl) (pull_request) Failing after 13s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Successful in 14m18s
DB-Integrationstests / db-tests (pull_request) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (pull_request) Failing after 58s
CI / Web (pdf-viewer) (pull_request) Failing after 57s
CI / Web (dms-browser) (pull_request) Failing after 1m22s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Successful in 14m30s
Workflow-Pflichttest-Gate / pflichttest-gate (pull_request) Successful in 15m9s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 4s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 5s
CI / Web (dms-browser) (push) Failing after 1m24s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 21s
CI / Web (pdf-viewer) (push) Failing after 1m5s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Successful in 4s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 11:22:55 +01:00
patrickandClaude Sonnet 5.5 88d9704469 dms: migrate aus common v0.2.0 (Tabellenname bleibt unveraendert)
CI / Web (pdf-viewer) (push) Failing after 1m8s
Supply-Chain-Scan (Go) / govulncheck (push) Failing after 59s
CI / Web (pdf-viewer) (pull_request) Failing after 1m2s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Failing after 1m29s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Failing after 2m7s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Successful in 13m25s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 3s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Successful in 13m26s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (pull_request) Failing after 58s
CI / Web (shl) (push) Failing after 11s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Failing after 20s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Successful in 4s
CI / Web (shl) (pull_request) Failing after 13s
CI / Web (dms-browser) (push) Failing after 1m22s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 6s
CI / Web (dms-browser) (pull_request) Failing after 1m19s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Successful in 14m17s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Successful in 4s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 28s
CI / Core-Modul (Build/Vet/Test) (push) Failing after 59s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 4s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Failing after 3s
DB-Integrationstests / db-tests (pull_request) Canceled after 0s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 11:22:02 +01:00
patrickandClaude Sonnet 5.5 3c2bc971e1 ai: migrate aus common v0.2.0 (Tabellenname bleibt unveraendert)
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 4s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 20s
CI / Web (dms-browser) (pull_request) Failing after 1m26s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Successful in 13m56s
CI / Web (dms-browser) (push) Failing after 1m22s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Failing after 59s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Successful in 14m0s
CI / Core-Modul (Build/Vet/Test) (pull_request) Failing after 57s
Supply-Chain-Scan (Go) / govulncheck (push) Failing after 1m4s
CI / Web (shl) (pull_request) Failing after 12s
AI-Pflichttest-Gate / pflichttest-gate (pull_request) Successful in 13m49s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Failing after 2s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Successful in 3s
CI / Core-Modul (Build/Vet/Test) (push) Failing after 58s
CI / Web (pdf-viewer) (pull_request) Failing after 1m5s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 3s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 4s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Failing after 1m51s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Successful in 13m47s
DB-Integrationstests / db-tests (pull_request) Canceled after 0s
CI / Web (pdf-viewer) (push) Failing after 1m0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Successful in 4s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Failing after 22s
CI / Web (shl) (push) Failing after 14s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 11:21:06 +01:00
patrickandClaude Sonnet 5.5 1bdcff0f75 connect: migrate aus common v0.2.0 (Tabellenname bleibt unveraendert)
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Successful in 13m46s
CI / Web (dms-browser) (pull_request) Failing after 1m18s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Failing after 32s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 4s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 21s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Successful in 13m28s
CI / Core-Modul (Build/Vet/Test) (pull_request) Failing after 1m3s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Failing after 20s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Failing after 50s
DB-Integrationstests / db-tests (pull_request) Canceled after 0s
CI / Web (pdf-viewer) (push) Failing after 1m2s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Successful in 4s
CI / Web (pdf-viewer) (pull_request) Failing after 1m4s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 4s
CI / Web (shl) (push) Failing after 11s
Supply-Chain-Scan (Go) / govulncheck (push) Failing after 1m7s
CI / Web (shl) (pull_request) Failing after 13s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Failing after 4s
CI / Core-Modul (Build/Vet/Test) (push) Failing after 1m1s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Successful in 13m37s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 5s
CI / Web (dms-browser) (push) Failing after 1m23s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Successful in 3s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Failing after 1m53s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 11:20:50 +01:00
patrick bec1fb3cae Merge pull request 'Root-Modul: apierror und httpharden aus common v0.1.0' (#396) from refactor/root-common into main
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (push) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (push) Canceled after 0s
CI / Web (shl) (push) Canceled after 0s
CI / Web (dms-browser) (push) Canceled after 0s
Core-Schnittstellen-Vertragstests / contract-tests (push) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Canceled after 0s
CI / Web (pdf-viewer) (push) Canceled after 0s
2026-10-28 10:41:53 +01:00
patrick b2c1a67728 Merge pull request 'workflow: svcauth, tenantregistryclient aus common v0.1.0' (#389) from refactor/workflow-common into main
CI / Core-Modul (Build/Vet/Test) (push) Canceled after 0s
Workflow-Schnittstellen-Vertragstests / contract-tests (push) Canceled after 0s
CI / Web (dms-browser) (push) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Canceled after 0s
CI / Web (pdf-viewer) (push) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
CI / Web (shl) (push) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (push) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
2026-10-28 10:41:45 +01:00
patrick 95582ad09d Merge pull request 'connect: auditclient, httpclient, policyclient aus common v0.1.0' (#388) from refactor/connect-common into main
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (push) Canceled after 0s
CI / Web (shl) (push) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (push) Canceled after 0s
Connect-Schnittstellen-Vertragstests / contract-tests (push) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
CI / Web (dms-browser) (push) Canceled after 0s
CI / Web (pdf-viewer) (push) Canceled after 0s
2026-10-28 10:41:39 +01:00
patrick a0f9aaef4c Merge pull request 'ai: auditclient, httpclient, loadtest, svcauth, tenantregistryclient aus common v0.1.0' (#387) from refactor/ai-common into main
CI / Web (pdf-viewer) (push) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (push) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
CI / Web (dms-browser) (push) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (push) Canceled after 0s
CI / Web (shl) (push) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Canceled after 0s
2026-10-28 10:41:32 +01:00
patrick a443911e6a Merge pull request 'mail: httpclient, loadtest, policyclient aus common v0.1.0' (#386) from refactor/mail-common into main
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (push) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (push) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
CI / Web (dms-browser) (push) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Canceled after 0s
Mail-Schnittstellen-Vertragstests / contract-tests (push) Canceled after 0s
CI / Web (pdf-viewer) (push) Canceled after 0s
CI / Web (shl) (push) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Canceled after 0s
2026-10-28 10:41:26 +01:00
patrick fbb41235c0 Merge pull request 'dms: apierror, httpclient, tenantregistryclient aus common v0.1.0' (#384) from refactor/dms-common-klein into main
CI / Web (pdf-viewer) (push) Canceled after 0s
CI / Web (shl) (push) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (push) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Canceled after 0s
CI / Web (dms-browser) (push) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (push) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Canceled after 0s
2026-10-28 10:41:19 +01:00
patrick 5b93ae8cae Merge pull request 'archive: Pilot-Umstellung auf common v0.1.0' (#383) from refactor/archive-common-pilot into main
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Canceled after 0s
CI / Web (shl) (push) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Canceled after 0s
CI / Web (dms-browser) (push) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
Archive-Schnittstellen-Vertragstests / contract-tests (push) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (push) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
CI / Web (pdf-viewer) (push) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (push) Canceled after 0s
2026-10-28 10:41:12 +01:00
patrick ad00c6258d Merge pull request 'common: moduleauth (Modul-Token-Verifier + Bearer-Middleware)' (#394) from feat/common-moduleauth into main
common Versions-Tag / tag (push) Successful in 13m39s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (push) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
CI / Web (dms-browser) (push) Canceled after 0s
CI / Web (pdf-viewer) (push) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (push) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Canceled after 0s
CI / Web (shl) (push) Canceled after 0s
2026-10-28 10:41:06 +01:00
patrick 2e9ec88b81 Merge pull request 'common v0.2.0: migrate mit Tabellenname als Parameter' (#385) from feat/common-migrate-v02 into main
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (push) Canceled after 0s
CI / Web (dms-browser) (push) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Web (shl) (push) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
CI / Web (pdf-viewer) (push) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (push) Canceled after 0s
common Versions-Tag / tag (push) Successful in 13m48s
2026-10-28 10:40:59 +01:00
patrickandClaude Sonnet 5.5 5f1d43caf1 CI-Cluster: Admin-DSN auf Datenbank postgres (Root-Tests ersetzen /postgres? im DSN)
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
CI / Web (dms-browser) (pull_request) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Canceled after 0s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Canceled after 0s
CI / Web (shl) (pull_request) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Canceled after 0s
DB-Integrationstests / db-tests (pull_request) Canceled after 0s
CI / Web (shl) (push) Canceled after 0s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Canceled after 0s
CI / Web (dms-browser) (push) Canceled after 0s
CI / Web (pdf-viewer) (push) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (push) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (pull_request) Canceled after 0s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Canceled after 0s
CI / Web (pdf-viewer) (pull_request) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Canceled after 0s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 10:28:26 +01:00
patrickandClaude Sonnet 5.5 0d1721f169 dms: USAGE auf audit_log_id_seq fuer nexarch_app (Migration 0036), Test mit Schema-Rechten
Supply-Chain-Scan (Go) / govulncheck (pull_request) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Web (pdf-viewer) (pull_request) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Canceled after 0s
CI / Web (shl) (pull_request) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Canceled after 0s
CI / Web (dms-browser) (pull_request) Canceled after 0s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
CI / Web (pdf-viewer) (push) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (pull_request) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Canceled after 0s
CI / Web (shl) (push) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Canceled after 0s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Canceled after 0s
CI / Web (dms-browser) (push) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Canceled after 0s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (push) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (push) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Canceled after 0s
DB-Integrationstests / db-tests (pull_request) Canceled after 0s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 10:28:10 +01:00
patrickandClaude Sonnet 5.5 4e16d8b155 dms/upload: Sniff-Ergebnis gegen deklarierten Typ pruefen statt gegen die Positivliste
CI / Core-Modul (Build/Vet/Test) (push) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Canceled after 0s
CI / Web (pdf-viewer) (push) Canceled after 0s
CI / Web (pdf-viewer) (pull_request) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (push) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
DB-Integrationstests / db-tests (pull_request) Canceled after 0s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Web (dms-browser) (pull_request) Canceled after 0s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Canceled after 0s
CI / Web (shl) (push) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Canceled after 0s
CI / Web (dms-browser) (push) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Canceled after 0s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Canceled after 0s
CI / Web (shl) (pull_request) Canceled after 0s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (pull_request) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Canceled after 0s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 10:26:14 +01:00
patrickandClaude Sonnet 5.5 63d7e01948 mail: Vertragstest (Anhang-Content-Type, zwei Endpunkt-Faelle), Gate-Tests ohne .git/Root-Modul skippen
CI / Web (pdf-viewer) (push) Canceled after 0s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Canceled after 0s
Mail-Schnittstellen-Vertragstests / contract-tests (push) Canceled after 0s
CI / Web (dms-browser) (push) Canceled after 0s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Canceled after 0s
Mail-Schnittstellen-Vertragstests / contract-tests (pull_request) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Canceled after 0s
DB-Integrationstests / db-tests (pull_request) Canceled after 0s
Mail-Pflichttest-Gate / pflichttest-gate (pull_request) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (pull_request) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Canceled after 0s
CI / Web (shl) (push) Canceled after 0s
CI / Web (shl) (pull_request) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (push) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Canceled after 0s
CI / Web (pdf-viewer) (pull_request) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Canceled after 0s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (push) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Canceled after 0s
CI / Web (dms-browser) (pull_request) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 10:22:49 +01:00
patrickandClaude Sonnet 5.5 59c0b0a2d1 ai: archtest:allow fuer searchchatapi-a11y-fixture (wie die Schwester-Fixtures)
AI-Pflichttest-Gate / pflichttest-gate (pull_request) Canceled after 0s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (push) Canceled after 0s
CI / Web (shl) (pull_request) Canceled after 0s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
CI / Web (pdf-viewer) (push) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Canceled after 0s
CI / Web (shl) (push) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Canceled after 0s
CI / Web (dms-browser) (push) Canceled after 0s
CI / Web (pdf-viewer) (pull_request) Canceled after 0s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (push) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (pull_request) Canceled after 0s
DB-Integrationstests / db-tests (pull_request) Canceled after 0s
CI / Web (dms-browser) (pull_request) Canceled after 0s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Canceled after 0s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 10:21:22 +01:00
patrickandClaude Sonnet 5.5 631dd4080e CI: Wegwerf-Postgres je Lauf fuer DB-Tests, CODEOWNERS fuer common/
CI / Web (dms-browser) (push) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Canceled after 0s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Canceled after 0s
CI / Web (shl) (push) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Canceled after 0s
CI / Web (shl) (pull_request) Canceled after 0s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Canceled after 0s
CI / Web (dms-browser) (pull_request) Canceled after 0s
CI / Web (pdf-viewer) (pull_request) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (pull_request) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Canceled after 0s
CI / Web (pdf-viewer) (push) Canceled after 0s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (push) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
DB-Integrationstests / db-tests (pull_request) Canceled after 0s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Canceled after 0s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 10:19:58 +01:00
patrickandClaude Sonnet 5.5 7df4cd573f portal: Zielmodul beim Token-Abruf (NEXT_PUBLIC_TOKEN_MODULE), Betriebsdoku
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Canceled after 0s
CI / Web (dms-browser) (push) Canceled after 0s
CI / Web (shl) (pull_request) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (pull_request) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Web (dms-browser) (pull_request) Canceled after 0s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (push) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Canceled after 0s
CI / Web (pdf-viewer) (pull_request) Canceled after 0s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Canceled after 0s
CI / Web (shl) (push) Canceled after 0s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Canceled after 0s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Canceled after 0s
CI / Web (pdf-viewer) (push) Canceled after 0s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 10:19:06 +01:00
patrickandClaude Sonnet 5.5 07eb74cadf Root-Modul: apierror und httpharden aus common v0.1.0
CI / Web (dms-browser) (push) Failing after 1m26s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Failing after 21s
CI / Web (pdf-viewer) (pull_request) Failing after 1m2s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
CI / Web (shl) (pull_request) Failing after 11s
CI / Web (shl) (push) Failing after 11s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Canceled after 0s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 19s
CI / Web (pdf-viewer) (push) Failing after 1m3s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Successful in 4s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 4s
Supply-Chain-Scan (Go) / govulncheck (push) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Canceled after 0s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (push) Failing after 1m0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Successful in 5s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 5s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
DB-Integrationstests / db-tests (pull_request) Canceled after 0s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Canceled after 0s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Successful in 13m12s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (pull_request) Failing after 1m0s
CI / Web (dms-browser) (pull_request) Failing after 1m27s
Core-Schnittstellen-Vertragstests / contract-tests (push) Successful in 12m23s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 09:37:03 +01:00
patrickandClaude Sonnet 5.5 8d68895eac docs: Arbeitskarten auf Stand 2026-10-28
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 3s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Successful in 4s
CI / Web (dms-browser) (pull_request) Failing after 1m26s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Canceled after 0s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Successful in 12m30s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Successful in 3m9s
CI / Core-Modul (Build/Vet/Test) (push) Failing after 59s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 22s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Successful in 3s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Failing after 1m55s
CI / Web (shl) (push) Failing after 9s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Successful in 12m46s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
CI / Web (pdf-viewer) (push) Failing after 59s
CI / Web (dms-browser) (push) Failing after 1m28s
CI / Web (shl) (pull_request) Failing after 10s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Failing after 58s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 5s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Failing after 21s
CI / Web (pdf-viewer) (pull_request) Failing after 1m0s
CI / Core-Modul (Build/Vet/Test) (pull_request) Failing after 1m0s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-27 23:50:59 +01:00
patrickandClaude Sonnet 5.5 51b639625f common/moduleauth: Modul-Token-Verifier (Ed25519, JWKS) und Bearer-Middleware
CI / Web (dms-browser) (pull_request) Failing after 1m16s
CI / Core-Modul (Build/Vet/Test) (pull_request) Failing after 1m1s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Successful in 4s
CI / Web (pdf-viewer) (pull_request) Failing after 57s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 18s
CI / Web (shl) (push) Failing after 11s
CI / Web (shl) (pull_request) Failing after 11s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Failing after 52s
Supply-Chain-Scan (Go) / govulncheck (push) Failing after 57s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Failing after 21s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Successful in 12m16s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Failing after 21m17s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 3s
CI / Web (pdf-viewer) (push) Failing after 57s
CI / Core-Modul (Build/Vet/Test) (push) Failing after 55s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 3s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Failing after 3s
CI / Web (dms-browser) (push) Failing after 1m18s
common Pflichttest-Gate / pflichttest-gate (pull_request) Successful in 11m57s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Failing after 1m58s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Successful in 12m19s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 3s
DB-Integrationstests / db-tests (pull_request) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Successful in 4s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Failing after 31s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-27 23:40:30 +01:00
patrickandClaude Sonnet 5.5 678bd01335 workflow: testdb aus common statt interner Kopie
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
CI / Web (pdf-viewer) (pull_request) Failing after 59s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 4s
CI / Core-Modul (Build/Vet/Test) (pull_request) Failing after 1m1s
CI / Web (shl) (pull_request) Failing after 13s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 24s
CI / Web (dms-browser) (push) Failing after 1m23s
CI / Web (pdf-viewer) (push) Failing after 59s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Failing after 2m6s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Successful in 3m51s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Successful in 11m50s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Successful in 11m41s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 3s
DB-Integrationstests / db-tests (pull_request) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Successful in 3s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Failing after 56s
Workflow-Schnittstellen-Vertragstests / contract-tests (pull_request) Failing after 41s
CI / Core-Modul (Build/Vet/Test) (push) Failing after 1m0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Successful in 4s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Failing after 3s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 4s
CI / Web (shl) (push) Failing after 13s
Supply-Chain-Scan (Go) / govulncheck (push) Failing after 56s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Failing after 21s
Workflow-Schnittstellen-Vertragstests / contract-tests (push) Failing after 59s
CI / Web (dms-browser) (pull_request) Failing after 1m17s
Workflow-Pflichttest-Gate / pflichttest-gate (pull_request) Successful in 11m18s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-27 23:38:24 +01:00
patrickandClaude Sonnet 5.5 f5f0bd8673 connect: testdb aus common statt interner Kopie
CI / Core-Modul (Build/Vet/Test) (pull_request) Failing after 58s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Successful in 4s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Successful in 11m40s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Failing after 3s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 5s
Supply-Chain-Scan (Go) / govulncheck (push) Failing after 1m10s
CI / Core-Modul (Build/Vet/Test) (push) Failing after 57s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 5s
Connect-Schnittstellen-Vertragstests / contract-tests (pull_request) Successful in 12m3s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Failing after 1m49s
CI / Web (dms-browser) (push) Failing after 1m23s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Failing after 24s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Failing after 1m11s
CI / Web (pdf-viewer) (push) Failing after 55s
CI / Web (shl) (push) Failing after 13s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Successful in 4s
CI / Web (dms-browser) (pull_request) Failing after 1m25s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Web (pdf-viewer) (pull_request) Failing after 54s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Successful in 11m49s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 22s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Successful in 3m23s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 3s
DB-Integrationstests / db-tests (pull_request) Canceled after 0s
Connect-Schnittstellen-Vertragstests / contract-tests (push) Successful in 10m42s
CI / Web (shl) (pull_request) Failing after 11s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-27 23:38:01 +01:00
patrickandClaude Sonnet 5.5 0854d7b040 ai: testdb aus common statt interner Kopie
Supply-Chain-Scan (Go) / govulncheck (pull_request) Failing after 1m12s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Failing after 3s
CI / Web (pdf-viewer) (pull_request) Failing after 1m0s
CI / Web (dms-browser) (push) Failing after 1m21s
CI / Web (shl) (push) Failing after 10s
CI / Web (dms-browser) (pull_request) Failing after 1m30s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Successful in 4s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 3s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 19s
AI-Pflichttest-Gate / pflichttest-gate (pull_request) Successful in 11m55s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Successful in 3m9s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 5s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 5s
CI / Core-Modul (Build/Vet/Test) (push) Failing after 57s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Successful in 11m59s
AI-Schnittstellen-Vertragstests / contract-tests (push) Successful in 10m0s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Successful in 11m12s
Supply-Chain-Scan (Go) / govulncheck (push) Failing after 2m57s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Successful in 4s
CI / Web (shl) (pull_request) Failing after 11s
AI-Schnittstellen-Vertragstests / contract-tests (pull_request) Successful in 12m17s
CI / Core-Modul (Build/Vet/Test) (pull_request) Failing after 1m6s
DB-Integrationstests / db-tests (pull_request) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Failing after 21s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Failing after 1m48s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Web (pdf-viewer) (push) Failing after 52s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-27 23:37:39 +01:00
patrickandClaude Sonnet 5.5 0c6a943440 dms: testdb aus common statt interner Kopie
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
CI / Web (dms-browser) (push) Failing after 1m22s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Successful in 4s
CI / Web (pdf-viewer) (pull_request) Failing after 57s
CI / Web (shl) (pull_request) Failing after 12s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Failing after 21s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Failing after 3s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 2s
DB-Integrationstests / db-tests (pull_request) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 5s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Failing after 1m46s
Supply-Chain-Scan (Go) / govulncheck (push) Failing after 1m6s
CI / Core-Modul (Build/Vet/Test) (push) Failing after 1m2s
CI / Web (shl) (push) Failing after 11s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Successful in 3s
CI / Web (dms-browser) (pull_request) Failing after 1m22s
CI / Core-Modul (Build/Vet/Test) (pull_request) Failing after 1m0s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Failing after 3m16s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Successful in 10m56s
CI / Web (pdf-viewer) (push) Failing after 53s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Successful in 11m44s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Successful in 11m24s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 22s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 5s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-27 23:37:00 +01:00
patrickandClaude Sonnet 5.5 eaffcaf885 workflow: svcauth, tenantregistryclient aus common v0.1.0
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Successful in 7s
CI / Web (dms-browser) (pull_request) Failing after 1m24s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Successful in 8s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Successful in 9m47s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Failing after 3m26s
Workflow-Schnittstellen-Vertragstests / contract-tests (pull_request) Failing after 57s
CI / Web (shl) (pull_request) Failing after 11s
CI / Web (dms-browser) (push) Failing after 1m31s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 3s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Failing after 3s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 4s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Failing after 30s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 3s
CI / Web (pdf-viewer) (push) Failing after 1m3s
CI / Web (pdf-viewer) (pull_request) Failing after 57s
CI / Core-Modul (Build/Vet/Test) (pull_request) Failing after 11m29s
CI / Core-Modul (Build/Vet/Test) (push) Failing after 1m6s
Supply-Chain-Scan (Go) / govulncheck (push) Failing after 4m11s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Successful in 10m30s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Successful in 10m25s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 23s
CI / Web (shl) (push) Failing after 14s
Workflow-Schnittstellen-Vertragstests / contract-tests (push) Failing after 41s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Failing after 1m57s
Workflow-Pflichttest-Gate / pflichttest-gate (pull_request) Failing after 1m13s
DB-Integrationstests / db-tests (pull_request) Canceled after 0s
Das Pflichttest-Gate von workflow verweist nicht mehr auf die geloeschten
Pakete (common hat ein eigenes Gate).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-27 23:33:55 +01:00
patrickandClaude Sonnet 5.5 7958102870 connect: auditclient, httpclient, policyclient aus common v0.1.0
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
CI / Web (shl) (pull_request) Failing after 15s
DB-Integrationstests / db-tests (pull_request) Canceled after 0s
Connect-Schnittstellen-Vertragstests / contract-tests (push) Successful in 10m0s
CI / Core-Modul (Build/Vet/Test) (pull_request) Failing after 1m4s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Failing after 25s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Successful in 3m28s
CI / Core-Modul (Build/Vet/Test) (push) Failing after 1m4s
CI / Web (dms-browser) (push) Failing after 1m31s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 4s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Failing after 1m0s
Supply-Chain-Scan (Go) / govulncheck (push) Failing after 45s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Failing after 3s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 4s
CI / Web (shl) (push) Failing after 12s
Connect-Schnittstellen-Vertragstests / contract-tests (pull_request) Successful in 11m18s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Failing after 2m4s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Successful in 3s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Successful in 10m25s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 25s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Successful in 10m55s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Web (pdf-viewer) (push) Failing after 1m2s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Successful in 3s
CI / Web (pdf-viewer) (pull_request) Failing after 1m4s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 3s
CI / Web (dms-browser) (pull_request) Failing after 1m33s
auditclient und policyclient aus der Liste in check-security-test-coverage.sh
entfernt (Pakete liegen jetzt in common, dort durch das common-Gate gedeckt).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-27 23:33:13 +01:00
patrickandClaude Sonnet 5.5 ff5113f0b2 ai: auditclient, httpclient, loadtest, svcauth, tenantregistryclient aus common v0.1.0
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Failing after 24s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 5s
Supply-Chain-Scan (Go) / govulncheck (push) Failing after 50s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 5s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Failing after 2m1s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Failing after 2s
CI / Web (pdf-viewer) (push) Failing after 1m6s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Successful in 6s
CI / Web (dms-browser) (pull_request) Failing after 1m31s
CI / Web (shl) (pull_request) Failing after 11s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 23s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 3s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Failing after 7m19s
CI / Web (pdf-viewer) (pull_request) Failing after 1m2s
CI / Web (shl) (push) Failing after 14s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Successful in 10m32s
CI / Web (dms-browser) (push) Failing after 1m32s
AI-Pflichttest-Gate / pflichttest-gate (pull_request) Failing after 44s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Successful in 5s
DB-Integrationstests / db-tests (pull_request) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Failing after 51s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Successful in 13m10s
CI / Core-Modul (Build/Vet/Test) (push) Failing after 1m2s
CI / Core-Modul (Build/Vet/Test) (pull_request) Failing after 1m7s
Das Pflichttest-Gate von ai verweist nicht mehr auf das geloeschte
svcauth-Paket (common hat ein eigenes Gate).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-27 23:32:34 +01:00
patrickandClaude Sonnet 5.5 27681f31be mail: httpclient, loadtest, policyclient aus common v0.1.0
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 4s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Successful in 9m7s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Failing after 3s
CI / Core-Modul (Build/Vet/Test) (pull_request) Failing after 1m8s
Mail-Schnittstellen-Vertragstests / contract-tests (pull_request) Failing after 45s
DB-Integrationstests / db-tests (pull_request) Canceled after 0s
CI / Web (pdf-viewer) (push) Failing after 1m9s
Mail-Pflichttest-Gate / pflichttest-gate (pull_request) Successful in 9m36s
CI / Core-Modul (Build/Vet/Test) (push) Failing after 1m5s
CI / Web (dms-browser) (push) Failing after 1m32s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Successful in 4s
Mail-Schnittstellen-Vertragstests / contract-tests (push) Failing after 1m3s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 3s
CI / Web (shl) (push) Failing after 14s
CI / Web (pdf-viewer) (pull_request) Failing after 1m8s
CI / Web (dms-browser) (pull_request) Failing after 1m34s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Failing after 22s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Failing after 1m4s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 23s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Successful in 5s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Successful in 9m13s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 4s
Supply-Chain-Scan (Go) / govulncheck (push) Failing after 55s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Failing after 2m5s
CI / Web (shl) (pull_request) Failing after 12s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Successful in 3m9s
Der Waechtertest gegen http.DefaultClient liegt jetzt in internal/archtest.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-27 23:29:11 +01:00
patrickandClaude Sonnet 5.5 f8905e9c64 common/migrate v0.2.0: Tabellenname als Parameter (migrate.New), Tag-Workflow mit common/MINOR
CI / Web (dms-browser) (push) Failing after 1m28s
Supply-Chain-Scan (Go) / govulncheck (push) Failing after 2m46s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Failing after 3s
CI / Web (dms-browser) (pull_request) Failing after 1m27s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 2s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 4s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Successful in 2m56s
DB-Integrationstests / db-tests (pull_request) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Failing after 20s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Successful in 9m12s
CI / Web (pdf-viewer) (pull_request) Failing after 1m2s
CI / Web (pdf-viewer) (push) Failing after 1m8s
CI / Core-Modul (Build/Vet/Test) (pull_request) Failing after 1m2s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 25s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Successful in 3m3s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Failing after 3m4s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Successful in 4s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Successful in 5s
CI / Web (shl) (push) Failing after 15s
CI / Web (shl) (pull_request) Failing after 13s
common Pflichttest-Gate / pflichttest-gate (pull_request) Successful in 9m21s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Failing after 1m57s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 4s
CI / Core-Modul (Build/Vet/Test) (push) Failing after 1m4s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-27 23:26:27 +01:00
patrickandClaude Sonnet 5.5 0df4696e8c dms: apierror, httpclient, tenantregistryclient aus common v0.1.0
CI / Core-Modul (Build/Vet/Test) (push) Failing after 1m7s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Successful in 4s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Failing after 3m4s
CI / Web (pdf-viewer) (push) Failing after 1m2s
CI / Web (shl) (push) Failing after 14s
CI / Web (dms-browser) (push) Failing after 1m34s
CI / Web (pdf-viewer) (pull_request) Failing after 1m2s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Successful in 2m39s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 3s
CI / Core-Modul (Build/Vet/Test) (pull_request) Failing after 1m9s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Failing after 21s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 5s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 23s
CI / Web (shl) (pull_request) Failing after 12s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Successful in 8m37s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Failing after 2s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Web (dms-browser) (pull_request) Failing after 1m30s
Supply-Chain-Scan (Go) / govulncheck (push) Failing after 2m46s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Successful in 4s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Successful in 2m49s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 5s
DB-Integrationstests / db-tests (pull_request) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Failing after 2m6s
Die common-Fassung von apierror enthaelt die 5xx-Behebung (Text verborgen,
Korrelations-ID). Der Waechtertest gegen http.DefaultClient liegt jetzt in
internal/archtest.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-27 23:23:56 +01:00
patrickandClaude Sonnet 5.5 de90c3d935 archive: gemeinsame Pakete aus common v0.1.0 statt interner Kopien
Supply-Chain-Scan (Go) / govulncheck (pull_request) Failing after 3m11s
CI / Web (dms-browser) (push) Failing after 1m40s
CI / Web (shl) (pull_request) Failing after 13s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Successful in 2m41s
Archive-Pflichttest-Gate / pflichttest-gate (pull_request) Failing after 2m57s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Failing after 2m1s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 5s
CI / Web (shl) (push) Failing after 13s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
Archive-Schnittstellen-Vertragstests / contract-tests (pull_request) Successful in 3m7s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 6s
CI / Web (pdf-viewer) (push) Failing after 1m4s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Successful in 2m39s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Successful in 4s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Successful in 2m42s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Failing after 3s
DB-Integrationstests / db-tests (pull_request) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (push) Failing after 1m1s
CI / Core-Modul (Build/Vet/Test) (pull_request) Failing after 1m5s
CI / Web (dms-browser) (pull_request) Failing after 1m43s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Failing after 33s
Supply-Chain-Scan (Go) / govulncheck (push) Failing after 2m41s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Failing after 26s
CI / Web (pdf-viewer) (pull_request) Failing after 1m9s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 20s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Successful in 4s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 3s
Archive-Schnittstellen-Vertragstests / contract-tests (push) Successful in 8m26s
apierror, auditclient, httpclient, httpharden, svcauth und tenantregistryclient
kommen aus gitea.perlbach24.de/scripte/nexarch/common. Der Wächtertest gegen
http.DefaultClient liegt jetzt in internal/archtest.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-27 23:19:13 +01:00
patrick 184a219c8a Merge pull request 'SHL-04: AccountBar-Dopplung aufgeloest' (#372) from feature/shl-03-accountbar-dedup into main
CI / Core-Modul (Build/Vet/Test) (push) Canceled after 0s
CI / Web (shl) (push) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Canceled after 0s
CI / Web (pdf-viewer) (push) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
CI / Web (dms-browser) (push) Canceled after 0s
2026-10-27 23:07:29 +01:00
patrick 118a524cec Merge pull request 'Portal: Gruppen, Arbeitsvorrat, Betriebsblock, Mandant aus Sitzung' (#382) from feature/portal-ausbau into feature/shl-03-accountbar-dedup
CI / Web (dms-browser) (pull_request) Canceled after 0s
CI / Web (shl) (push) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Canceled after 0s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (push) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (pull_request) Canceled after 0s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Canceled after 0s
CI / Web (dms-browser) (push) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Canceled after 0s
CI / Web (shl) (pull_request) Canceled after 0s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Canceled after 0s
CI / Web (pdf-viewer) (push) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Canceled after 0s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Canceled after 0s
CI / Web (pdf-viewer) (pull_request) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
2026-10-27 23:06:34 +01:00
patrickandClaude Sonnet 5.5 ab826dac3c portal: Betriebsblock (Systemstatus, Kennzahlen-Summe), Caches und Policy aus den Route-Dateien ausgelagert
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Canceled after 0s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (pull_request) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Canceled after 0s
CI / Web (pdf-viewer) (pull_request) Canceled after 0s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Canceled after 0s
CI / Web (dms-browser) (pull_request) Canceled after 0s
CI / Web (dms-browser) (push) Canceled after 0s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Canceled after 0s
CI / Web (shl) (pull_request) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (push) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Canceled after 0s
CI / Web (pdf-viewer) (push) Canceled after 0s
CI / Web (shl) (push) Canceled after 0s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-27 23:05:44 +01:00
patrickandClaude Sonnet 5.5 d5acbe1029 portal: Gruppen, Arbeitsvorrat, Mandant aus Sitzung, URL-Override; Arbeitskarten
CI / Core-Modul (Build/Vet/Test) (push) Canceled after 0s
CI / Web (dms-browser) (push) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
CI / Web (shl) (push) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Canceled after 0s
CI / Web (pdf-viewer) (push) Canceled after 0s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-27 23:02:36 +01:00
patrickandClaude Sonnet 5.5 6b42de39b9 portal: README mit Umgebungsvariablen und Verhalten der Route
CI / Web (dms-browser) (push) Canceled after 0s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Canceled after 0s
CI / Web (dms-browser) (pull_request) Canceled after 0s
CI / Web (pdf-viewer) (push) Canceled after 0s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (pull_request) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Canceled after 0s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (push) Canceled after 0s
CI / Web (shl) (pull_request) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Canceled after 0s
CI / Web (shl) (push) Canceled after 0s
CI / Web (pdf-viewer) (pull_request) Canceled after 0s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-27 22:59:03 +01:00
patrickandClaude Sonnet 5.5 fd4a0568a3 portal: visible-modules prueft Modul-Token (Mandant/Rolle aus Claims), 30s-Cache je Mandant/Rolle
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Canceled after 0s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
CI / Web (shl) (push) Canceled after 0s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Canceled after 0s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Canceled after 0s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (push) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Canceled after 0s
CI / Web (dms-browser) (pull_request) Canceled after 0s
CI / Web (shl) (pull_request) Canceled after 0s
CI / Web (pdf-viewer) (pull_request) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Web (dms-browser) (push) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (pull_request) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Canceled after 0s
CI / Web (pdf-viewer) (push) Canceled after 0s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-27 22:49:22 +01:00
patrickandClaude Sonnet 5.5 65cc0ac7bf portal: AccountBar mit Benutzer/Rolle/Mandant, kein Neuladen der Module beim Sprachwechsel
Supply-Chain-Scan (npm) / npm-audit (pull_request) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Canceled after 0s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Canceled after 0s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Canceled after 0s
CI / Web (dms-browser) (push) Canceled after 0s
CI / Web (shl) (pull_request) Canceled after 0s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (pull_request) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (push) Canceled after 0s
CI / Web (shl) (push) Canceled after 0s
CI / Web (dms-browser) (pull_request) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Canceled after 0s
CI / Web (pdf-viewer) (pull_request) Canceled after 0s
CI / Web (pdf-viewer) (push) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Canceled after 0s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-27 22:46:35 +01:00
patrickandClaude Sonnet 5.5 bc47c1331b SHL-04: AccountBar zeigt Benutzer, Rolle und Mandant aus der Sitzung
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Canceled after 0s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (push) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Canceled after 0s
CI / Web (pdf-viewer) (push) Canceled after 0s
CI / Web (shl) (push) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Canceled after 0s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Canceled after 0s
CI / Web (dms-browser) (pull_request) Canceled after 0s
CI / Web (dms-browser) (push) Canceled after 0s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (pull_request) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Web (pdf-viewer) (pull_request) Canceled after 0s
CI / Web (shl) (pull_request) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Canceled after 0s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-27 22:31:17 +01:00
patrick b775de651b Merge pull request 'common/testdb: AdminDSN mit REQUIRE_DB + DB-Test-Workflow' (#381) from feat/common-requiredb into main
CI / Web (dms-browser) (push) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (push) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (push) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
CI / Web (pdf-viewer) (push) Canceled after 0s
common Versions-Tag / tag (push) Successful in 7m50s
CI / Web (shl) (push) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Canceled after 0s
2026-10-27 18:15:39 +01:00
patrick 8db663b7db Merge pull request 'feat(common): parametrisiertes Pflichttest-Gate für common/' (#379) from feat/common-pflichttestgate into main
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
common Versions-Tag / tag (push) Successful in 7m1s
CI / Web (dms-browser) (push) Canceled after 0s
CI / Web (shl) (push) Canceled after 0s
CI / Web (pdf-viewer) (push) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (push) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (push) Canceled after 0s
2026-10-27 18:15:30 +01:00
patrick 0b90c8b339 Merge pull request 'feat: versioniertes Modul common/ (ohne Verbraucher)' (#378) from feat/common-modul into main
CI / Web (pdf-viewer) (push) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (push) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Canceled after 0s
CI / Web (dms-browser) (push) Canceled after 0s
CI / Web (shl) (push) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
common Versions-Tag / tag (push) Successful in 7m23s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (push) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Canceled after 0s
2026-10-27 18:15:03 +01:00
patrickandClaude Sonnet 5.5 3f4ac77b1e common/testdb: AdminDSN mit REQUIRE_DB, DB-Test-Workflow fuer Runner nexarch-test
DB-Integrationstests / db-tests (pull_request) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Canceled after 0s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Canceled after 0s
CI / Web (shl) (push) Canceled after 0s
CI / Web (dms-browser) (push) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (push) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (push) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (pull_request) Canceled after 0s
CI / Web (pdf-viewer) (pull_request) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Web (shl) (pull_request) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Canceled after 0s
CI / Web (dms-browser) (pull_request) Canceled after 0s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Canceled after 0s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Canceled after 0s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Canceled after 0s
CI / Web (pdf-viewer) (push) Canceled after 0s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-27 17:58:25 +01:00
patrickandClaude Sonnet 5.5 843825abff docs: Vorbildsysteme (Mail + DMS) in PRODUKTARCHITEKTUR, DEVLOGs nachgezogen
CI / Web (pdf-viewer) (pull_request) Canceled after 0s
CI / Web (shl) (push) Canceled after 0s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Canceled after 0s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Canceled after 0s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (push) Canceled after 0s
CI / Web (dms-browser) (push) Canceled after 0s
CI / Web (dms-browser) (pull_request) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Canceled after 0s
CI / Web (pdf-viewer) (push) Canceled after 0s
CI / Web (shl) (pull_request) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (pull_request) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Canceled after 0s
Abschnitt 1a: Mail (archivmail, MailPiler), DMS (paperless-ngx, Alfresco,
Docspell, ecoDMS, archivdms); weitere Vergleichsprojekte als offen markiert.
DEVLOG.md, dms/DEVLOG.md, web/shl/DEVLOG.md: Session-Protokolle.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-27 15:47:35 +01:00
patrickandClaude Sonnet 5.5 0c8b3fe2b3 feat(common): parametrisiertes Pflichttest-Gate, Gate fuer common/ selbst
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Canceled after 0s
CI / Web (dms-browser) (pull_request) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (push) Canceled after 0s
CI / Web (pdf-viewer) (push) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Canceled after 0s
CI / Web (shl) (pull_request) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (pull_request) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Canceled after 0s
CI / Web (dms-browser) (push) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (push) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Web (pdf-viewer) (pull_request) Canceled after 0s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Canceled after 0s
common Pflichttest-Gate / pflichttest-gate (pull_request) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Canceled after 0s
CI / Web (shl) (push) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Canceled after 0s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Canceled after 0s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Canceled after 0s
CheckDiff(changed, patterns): gleiche Logik wie die Modul-Gates, Pfadmuster
vom Aufrufer. cmd/pflichttestgate + Workflow decken svcauth,
tenantregistryclient, auditclient, policyclient, apierror, httpharden ab.
README: Auswirkungsliste und Minor-Version bei Breaking Changes.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-27 15:25:26 +01:00
patrickandClaude Sonnet 5.5 4f07bc73a2 feat: versioniertes Modul common/ (ohne Verbraucher)
CI / Core-Modul (Build/Vet/Test) (push) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Canceled after 0s
CI / Web (pdf-viewer) (push) Canceled after 0s
CI / Web (shl) (pull_request) Canceled after 0s
CI / Web (pdf-viewer) (pull_request) Canceled after 0s
CI / Web (shl) (push) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (pull_request) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Canceled after 0s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Canceled after 0s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (push) Canceled after 0s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Canceled after 0s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Canceled after 0s
CI / Web (dms-browser) (pull_request) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Canceled after 0s
CI / Web (dms-browser) (push) Canceled after 0s
Pakete: apierror, auditclient, httpclient, httpharden, loadtest, migrate,
policyclient, svcauth, tenantregistryclient, testdb. Code stammt aus den
bisher byte-gleichen Modulkopien (ohne Kommentare/Importpfade verglichen).
migrate/testdb brauchen pgx v5.9.2.

Auslieferung per Git-Tag common/vX.Y.Z (Workflow common-tag.yml, setzt nach
Merge in main den naechsten Patch-Tag nach Build/Vet/Test). Kein replace,
damit Module einzeln baubar bleiben. Verbraucher werden in Folge-PRs
umgestellt.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-27 14:40:46 +01:00
patrickandClaude Sonnet 5.5 35911bfd6f fix(workflow): contracttest an moduleadapter.NewServer-Signatur angepasst
Supply-Chain-Scan (Go) / govulncheck (push) Failing after 35s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 24s
CI / Web (dms-browser) (pull_request) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
CI / Web (shl) (push) Failing after 14s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (push) Failing after 1m3s
Workflow-Schnittstellen-Vertragstests / contract-tests (push) Successful in 6m47s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Canceled after 0s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 4s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Canceled after 0s
CI / Web (pdf-viewer) (pull_request) Canceled after 0s
Workflow-Pflichttest-Gate / pflichttest-gate (pull_request) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Canceled after 0s
CI / Web (shl) (pull_request) Canceled after 0s
CI / Web (pdf-viewer) (push) Failing after 1m1s
Workflow-Schnittstellen-Vertragstests / contract-tests (pull_request) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 4s
CI / Core-Modul (Build/Vet/Test) (pull_request) Canceled after 0s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Canceled after 0s
CI / Web (dms-browser) (push) Failing after 1m25s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Canceled after 0s
NewServer verlangt seit ENG-06 einen TenantVerifier; der Vertragstest
rief noch mit drei Argumenten auf, go vet schlug fehl. Stub wie beim
backchannel-Vertragstest (alwaysValidVerifier).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-27 12:54:20 +01:00
patrickandClaude Sonnet 5.5 4497da760d style: gofmt in drei beruehrten Dateien (reine Formatierung, schon auf main unformatiert)
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 56s
Supply-Chain-Scan (Go) / govulncheck (push) Failing after 51s
CI / Web (dms-browser) (pull_request) Failing after 1m36s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (pull_request) Failing after 1m3s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Successful in 2m53s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Canceled after 0s
CI / Web (dms-browser) (push) Failing after 1m29s
CI / Web (shl) (push) Failing after 14s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 4s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Failing after 33s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Successful in 3s
CI / Web (shl) (pull_request) Failing after 17s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Failing after 2m0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Failing after 23s
AI-Pflichttest-Gate / pflichttest-gate (pull_request) Successful in 2m35s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Failing after 2m46s
Workflow-Pflichttest-Gate / pflichttest-gate (pull_request) Canceled after 0s
CI / Web (pdf-viewer) (push) Failing after 1m8s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Successful in 4s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Successful in 2m36s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Successful in 4s
CI / Web (pdf-viewer) (pull_request) Failing after 1m14s
CI / Core-Modul (Build/Vet/Test) (push) Failing after 1m5s
Archive-Pflichttest-Gate / pflichttest-gate (pull_request) Successful in 2m38s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Canceled after 0s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-27 12:51:57 +01:00
patrickandClaude Sonnet 5.5 5b8c20777d refactor: Pruefgate-Pakete nach Funktion benannt, migration* gruppiert
- dms/ai/workflow: qa0x-Pakete nach internal/gates/<thema> verschoben, Paketname = Thema
- internal/migration{acl,audit,...} -> internal/migration/{acl,audit,...}
- Importpfade, Gate-Skripte und zwei relative Migrationspfade angepasst
- keine Logikaenderung; mail-Gates bewusst unveraendert (pfadabhaengig)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-27 12:44:21 +01:00
patrickandClaude Sonnet 5 2518160c92 SHL-04: AccountBar-Dopplung aufgeloest (duenne Wrapper statt kopierter JSX)
CI / Core-Modul (Build/Vet/Test) (pull_request) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Canceled after 0s
CI / Web (shl) (push) Canceled after 0s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Canceled after 0s
CI / Web (shl) (pull_request) Canceled after 0s
CI / Web (pdf-viewer) (push) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Canceled after 0s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
CI / Web (dms-browser) (pull_request) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (push) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Canceled after 0s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
CI / Web (pdf-viewer) (pull_request) Canceled after 0s
CI / Web (dms-browser) (push) Canceled after 0s
Fable-Review auf Code-Dopplung (P1). web/dms-browser, web/dms-upload,
web/pdf-viewer hatten byteidentische components/AccountBar.tsx. Darstellung
jetzt einmal in web/shl/components/AccountBar.tsx (visible/onLogout als
Props). Die drei App-lokalen Dateien bleiben als duenne 10-Zeilen-Wrapper
bestehen (loesen authMode()/getSession() aus dem jeweils eigenen,
unterschiedlichen lib/http.ts auf) statt komplett zu verschwinden --
direktes Reichen der onLogout-Funktion von layout.tsx (Server Component)
an die shl-Komponente verletzt sonst die RSC-Props-Grenze ("Event handlers
cannot be passed to Client Component props"), erst beim Build entdeckt
und korrigiert.

next build + npx vitest run auf allen 3 Apps gruen (Testhost 131).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-19 23:17:02 +02:00
563 changed files with 10121 additions and 3626 deletions

No files matched your search

+2
View File
@@ -0,0 +1,2 @@
# common/ ist modulübergreifend: Änderungen brauchen Review (Auswirkungsliste im PR)
/common/ @patrick
@@ -0,0 +1,23 @@
name: common Pflichttest-Gate
on:
pull_request:
paths:
- "common/**"
jobs:
pflichttest-gate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-go@v5
with:
go-version: "1.25"
- name: Gate bauen
working-directory: common
run: go build -o /tmp/pflichttestgate ./cmd/pflichttestgate
- name: Geänderte Dateien gegen Pflichttest-Regel pruefen
run: |
git diff --name-only "origin/${{ github.base_ref }}...HEAD" -- common/ | /tmp/pflichttestgate
+33
View File
@@ -0,0 +1,33 @@
name: common Versions-Tag
on:
push:
branches: [main]
paths:
- "common/**"
jobs:
tag:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-go@v5
with:
go-version: "1.25"
- name: Build, Vet, Test
working-directory: common
run: |
go build ./...
go vet ./...
go test ./... -count=1
- name: Naechsten Tag setzen
run: |
minor=$(tr -d '[:space:]' < common/MINOR)
last=$(git tag -l "common/v0.${minor}.*" --sort=-v:refname | head -n1)
if [ -z "$last" ]; then next="common/v0.${minor}.0"; else
patch=${last##*.}; base=${last%.*}; next="${base}.$((patch + 1))"
fi
git tag "$next"
git push origin "$next"
+37
View File
@@ -0,0 +1,37 @@
name: DB-Integrationstests
on:
pull_request:
paths:
- "common/**"
- "archive/**"
- "ai/**"
- "connect/**"
- "scripts/ci-pg-cluster.sh"
- ".gitea/workflows/db-tests.yml"
jobs:
db-tests:
runs-on: nexarch-test
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: "1.25"
- name: Wegwerf-Postgres starten
run: scripts/ci-pg-cluster.sh start
- name: common
working-directory: common
run: go test ./... -count=1 -p 1
- name: archive
working-directory: archive
run: go test ./... -count=1 -p 1
- name: ai
working-directory: ai
run: go test ./... -count=1 -p 1
- name: connect
working-directory: connect
run: go test ./... -count=1 -p 1
- name: Wegwerf-Postgres stoppen
if: always()
run: scripts/ci-pg-cluster.sh stop
+564
View File
@@ -604,3 +604,567 @@ Keine Commits in dieser Session.
- scripts/qa02-foundation-gate.sh | 2 +-
---
## 2026-10-19 12:07 – 12:07 (0m)
**Beschreibung:** Claude Code Session
**Projekt:** nexarch
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- web/audit-log/app/page.tsx | 6 +++---
- web/dms-browser/app/layout.tsx | 6 +++---
- web/lic-admin/app/page.tsx | 18 +++++++++---------
- web/portal/app/layout.tsx | 12 ++----------
- web/portal/app/page.tsx | 4 ++--
- web/shl/components/ModuleNavShell.tsx | 18 +++++++++++++++---
- web/shl/components/Shell.tsx | 2 ++
- web/shl/index.ts | 2 +-
- web/shl/tokens/tokens.ts | 24 ++++++++++++++++++++++++
- web/status-page/app/page.tsx | 12 ++++++------
- web/tenant-admin/app/page.tsx | 8 ++++----
---
## 2026-10-19 12:08 – 12:08 (0m)
**Beschreibung:** Claude Code Session
**Projekt:** nexarch
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- web/audit-log/app/page.tsx | 6 +++---
- web/dms-browser/app/layout.tsx | 6 +++---
- web/lic-admin/app/page.tsx | 18 +++++++++---------
- web/portal/app/layout.tsx | 12 ++----------
- web/portal/app/page.tsx | 4 ++--
- web/shl/components/ModuleNavShell.tsx | 18 +++++++++++++++---
- web/shl/components/Shell.tsx | 2 ++
- web/shl/index.ts | 2 +-
- web/shl/tokens/tokens.ts | 24 ++++++++++++++++++++++++
- web/status-page/app/page.tsx | 12 ++++++------
- web/tenant-admin/app/page.tsx | 8 ++++----
---
## 2026-10-19 12:14 – 12:15 (1m)
**Beschreibung:** Claude Code Session
**Projekt:** nexarch
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- web/audit-log/app/page.tsx | 6 +++---
- web/dms-browser/app/layout.tsx | 6 +++---
- web/lic-admin/app/page.tsx | 18 +++++++++---------
- web/portal/app/layout.tsx | 12 ++----------
- web/portal/app/page.tsx | 4 ++--
- web/shl/components/ModuleNavShell.tsx | 18 +++++++++++++++---
- web/shl/components/Shell.tsx | 2 ++
- web/shl/index.ts | 2 +-
- web/shl/tokens/tokens.ts | 24 ++++++++++++++++++++++++
- web/status-page/app/page.tsx | 12 ++++++------
- web/tenant-admin/app/page.tsx | 8 ++++----
---
## 2026-10-19 12:29 – 12:30 (1m)
**Beschreibung:** Claude Code Session
**Projekt:** nexarch
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- dms/internal/upload/service.go | 38 ++++++++++++++++++++++++++++++---
- dms/internal/upload/service_test.go | 95 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
---
## 2026-10-19 12:31 – 12:31 (0m)
**Beschreibung:** Claude Code Session
**Projekt:** nexarch
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- dms/internal/upload/service.go | 25 ++++++++++++++++++++++++-
- dms/internal/upload/service_test.go | 44 ++++++++++++++++++++++++++++++++++++++++++++
---
## 2026-10-19 12:44 – 12:53 (8m)
**Beschreibung:** Claude Code Session
**Projekt:** banking
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- dms/internal/upload/service.go | 25 ++++++++++++++++++++++++-
- dms/internal/upload/service_test.go | 44 ++++++++++++++++++++++++++++++++++++++++++++
---
## 2026-10-19 12:54 – 12:54 (0m)
**Beschreibung:** Claude Code Session
**Projekt:** nexarch
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- dms/internal/upload/service.go | 25 ++++++++++++++++++++++++-
- dms/internal/upload/service_test.go | 44 ++++++++++++++++++++++++++++++++++++++++++++
---
## 2026-10-19 13:47 – 13:50 (2m)
**Beschreibung:** Claude Code Session
**Projekt:** banking
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- web/mail-postfach/app/layout.tsx | 8 +++++---
- web/shl/components/ModuleNavShell.tsx | 4 ++++
---
## 2026-10-19 14:49 – 14:50 (0m)
**Beschreibung:** Claude Code Session
**Projekt:** archivmail
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- MIGRATIONSKONZEPT.md | 2 +-
- PRODUKTARCHITEKTUR.md | 2 +-
- SICHERHEITSKONZEPT.md | 16 ++++++++--------
- SKALIERUNGSKONZEPT.md | 2 +-
- STORAGE-KONZEPT.md | 4 ++--
- dms/internal/searchsync/searchsync_test.go | 4 ++--
- web/portal/app/ModuleGrid.test.tsx | 3 ++-
- web/portal/app/ModuleGrid.tsx | 17 +----------------
- web/portal/lib/modules.test.ts | 4 +++-
- web/shl/components/ModuleNavShell.tsx | 27 +++++++++++++++++++--------
- web/shl/index.ts | 2 +-
---
## 2026-10-19 14:58 – 23:11 (8h 12m)
**Beschreibung:** Claude Code Session
**Projekt:** nexarch
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- web/dms-browser/components/AccountBar.tsx | 14 +++++++-------
- web/dms-upload/components/AccountBar.tsx | 14 +++++++-------
- web/pdf-viewer/components/AccountBar.tsx | 14 +++++++-------
- web/shl/components/AccountBar.tsx | 22 ++++++++++++++++++++++
- web/shl/index.ts | 3 +++
---
## 2026-10-20 04:03 – 04:03 (0m)
**Beschreibung:** Claude Code Session
**Projekt:** nexarch
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- web/dms-browser/components/AccountBar.tsx | 14 +++++++-------
- web/dms-upload/components/AccountBar.tsx | 14 +++++++-------
- web/pdf-viewer/components/AccountBar.tsx | 14 +++++++-------
- web/shl/components/AccountBar.tsx | 22 ++++++++++++++++++++++
- web/shl/index.ts | 3 +++
---
## 2026-10-20 04:03 – 04:03 (0m)
**Beschreibung:** Claude Code Session
**Projekt:** nexarch
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- web/dms-browser/components/AccountBar.tsx | 14 +++++++-------
- web/dms-upload/components/AccountBar.tsx | 14 +++++++-------
- web/pdf-viewer/components/AccountBar.tsx | 14 +++++++-------
- web/shl/components/AccountBar.tsx | 22 ++++++++++++++++++++++
- web/shl/index.ts | 3 +++
---
## 2026-10-20 04:03 – 04:03 (0m)
**Beschreibung:** Claude Code Session
**Projekt:** nexarch
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- web/dms-browser/components/AccountBar.tsx | 14 +++++++-------
- web/dms-upload/components/AccountBar.tsx | 14 +++++++-------
- web/pdf-viewer/components/AccountBar.tsx | 14 +++++++-------
- web/shl/components/AccountBar.tsx | 22 ++++++++++++++++++++++
- web/shl/index.ts | 3 +++
---
## 2026-10-20 04:03 – 04:03 (0m)
**Beschreibung:** Claude Code Session
**Projekt:** nexarch
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- web/dms-browser/components/AccountBar.tsx | 14 +++++++-------
- web/dms-upload/components/AccountBar.tsx | 14 +++++++-------
- web/pdf-viewer/components/AccountBar.tsx | 14 +++++++-------
- web/shl/components/AccountBar.tsx | 22 ++++++++++++++++++++++
- web/shl/index.ts | 3 +++
---
## 2026-10-20 04:03 – 04:03 (0m)
**Beschreibung:** Claude Code Session
**Projekt:** nexarch
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- web/dms-browser/components/AccountBar.tsx | 14 +++++++-------
- web/dms-upload/components/AccountBar.tsx | 14 +++++++-------
- web/pdf-viewer/components/AccountBar.tsx | 14 +++++++-------
- web/shl/components/AccountBar.tsx | 22 ++++++++++++++++++++++
- web/shl/index.ts | 3 +++
---
## 2026-10-27 11:18 – 11:18 (0m)
**Beschreibung:** Claude Code Session
**Projekt:** nexarch
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- web/dms-browser/components/AccountBar.tsx | 14 +++++++-------
- web/dms-upload/components/AccountBar.tsx | 14 +++++++-------
- web/pdf-viewer/components/AccountBar.tsx | 14 +++++++-------
- web/shl/components/AccountBar.tsx | 22 ++++++++++++++++++++++
- web/shl/index.ts | 3 +++
---
## 2026-10-27 11:20 – 11:20 (0m)
**Beschreibung:** Claude Code Session
**Projekt:** nexarch
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- web/dms-browser/components/AccountBar.tsx | 14 +++++++-------
- web/dms-upload/components/AccountBar.tsx | 14 +++++++-------
- web/pdf-viewer/components/AccountBar.tsx | 14 +++++++-------
- web/shl/components/AccountBar.tsx | 22 ++++++++++++++++++++++
- web/shl/index.ts | 3 +++
---
## 2026-10-27 11:54 – 11:54 (0m)
**Beschreibung:** Claude Code Session
**Projekt:** nexarch
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- web/dms-browser/components/AccountBar.tsx | 14 +++++++-------
- web/dms-upload/components/AccountBar.tsx | 14 +++++++-------
- web/pdf-viewer/components/AccountBar.tsx | 14 +++++++-------
- web/shl/components/AccountBar.tsx | 22 ++++++++++++++++++++++
- web/shl/index.ts | 3 +++
---
## 2026-10-27 11:55 – 11:55 (0m)
**Beschreibung:** Claude Code Session
**Projekt:** nexarch
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- web/dms-browser/components/AccountBar.tsx | 14 +++++++-------
- web/dms-upload/components/AccountBar.tsx | 14 +++++++-------
- web/pdf-viewer/components/AccountBar.tsx | 14 +++++++-------
- web/shl/components/AccountBar.tsx | 22 ++++++++++++++++++++++
- web/shl/index.ts | 3 +++
---
## 2026-10-27 11:57 – 11:57 (0m)
**Beschreibung:** Claude Code Session
**Projekt:** nexarch
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- web/dms-browser/components/AccountBar.tsx | 14 +++++++-------
- web/dms-upload/components/AccountBar.tsx | 14 +++++++-------
- web/pdf-viewer/components/AccountBar.tsx | 14 +++++++-------
- web/shl/components/AccountBar.tsx | 22 ++++++++++++++++++++++
- web/shl/index.ts | 3 +++
---
## 2026-10-27 11:58 – 11:58 (0m)
**Beschreibung:** Claude Code Session
**Projekt:** nexarch
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- web/dms-browser/components/AccountBar.tsx | 14 +++++++-------
- web/dms-upload/components/AccountBar.tsx | 14 +++++++-------
- web/pdf-viewer/components/AccountBar.tsx | 14 +++++++-------
- web/shl/components/AccountBar.tsx | 22 ++++++++++++++++++++++
- web/shl/index.ts | 3 +++
---
## 2026-10-27 12:37 – 12:38 (0m)
**Beschreibung:** Claude Code Session
**Projekt:** nexarch
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- web/dms-browser/components/AccountBar.tsx | 14 +++++++-------
- web/dms-upload/components/AccountBar.tsx | 14 +++++++-------
- web/pdf-viewer/components/AccountBar.tsx | 14 +++++++-------
- web/shl/components/AccountBar.tsx | 22 ++++++++++++++++++++++
- web/shl/index.ts | 3 +++
---
## 2026-10-27 12:38 – 12:38 (0m)
**Beschreibung:** Claude Code Session
**Projekt:** nexarch
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- web/dms-browser/components/AccountBar.tsx | 14 +++++++-------
- web/dms-upload/components/AccountBar.tsx | 14 +++++++-------
- web/pdf-viewer/components/AccountBar.tsx | 14 +++++++-------
- web/shl/components/AccountBar.tsx | 22 ++++++++++++++++++++++
- web/shl/index.ts | 3 +++
---
## 2026-10-27 12:40 – 12:40 (0m)
**Beschreibung:** Claude Code Session
**Projekt:** nexarch
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- web/dms-browser/components/AccountBar.tsx | 14 +++++++-------
- web/dms-upload/components/AccountBar.tsx | 14 +++++++-------
- web/pdf-viewer/components/AccountBar.tsx | 14 +++++++-------
- web/shl/components/AccountBar.tsx | 22 ++++++++++++++++++++++
- web/shl/index.ts | 3 +++
---
## 2026-10-27 12:42 – 12:42 (0m)
**Beschreibung:** Claude Code Session
**Projekt:** nexarch
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- web/dms-browser/components/AccountBar.tsx | 14 +++++++-------
- web/dms-upload/components/AccountBar.tsx | 14 +++++++-------
- web/pdf-viewer/components/AccountBar.tsx | 14 +++++++-------
- web/shl/components/AccountBar.tsx | 22 ++++++++++++++++++++++
- web/shl/index.ts | 3 +++
---
## 2026-10-27 12:42 – 12:44 (1m)
**Beschreibung:** Claude Code Session
**Projekt:** nexarch
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- web/dms-browser/components/AccountBar.tsx | 14 +++++++-------
- web/dms-upload/components/AccountBar.tsx | 14 +++++++-------
- web/pdf-viewer/components/AccountBar.tsx | 14 +++++++-------
- web/shl/components/AccountBar.tsx | 22 ++++++++++++++++++++++
- web/shl/index.ts | 3 +++
---
## 2026-10-27 12:47 – 12:52 (4m)
**Beschreibung:** Claude Code Session
**Projekt:** nexarch
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- web/dms-browser/components/AccountBar.tsx | 14 +++++++-------
- web/dms-upload/components/AccountBar.tsx | 14 +++++++-------
- web/pdf-viewer/components/AccountBar.tsx | 14 +++++++-------
- web/shl/components/AccountBar.tsx | 22 ++++++++++++++++++++++
- web/shl/index.ts | 3 +++
---
## 2026-10-27 12:53 – 12:54 (0m)
**Beschreibung:** Claude Code Session
**Projekt:** nexarch
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- web/dms-browser/components/AccountBar.tsx | 14 +++++++-------
- web/dms-upload/components/AccountBar.tsx | 14 +++++++-------
- web/pdf-viewer/components/AccountBar.tsx | 14 +++++++-------
- web/shl/components/AccountBar.tsx | 22 ++++++++++++++++++++++
- web/shl/index.ts | 3 +++
---
## 2026-10-27 12:55 – 12:56 (0m)
**Beschreibung:** Claude Code Session
**Projekt:** nexarch
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- web/dms-browser/components/AccountBar.tsx | 14 +++++++-------
- web/dms-upload/components/AccountBar.tsx | 14 +++++++-------
- web/pdf-viewer/components/AccountBar.tsx | 14 +++++++-------
- web/shl/components/AccountBar.tsx | 22 ++++++++++++++++++++++
- web/shl/index.ts | 3 +++
---
## 2026-10-27 13:00 – 13:01 (0m)
**Beschreibung:** Claude Code Session
**Projekt:** Trad-Bot
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- web/dms-browser/components/AccountBar.tsx | 14 +++++++-------
- web/dms-upload/components/AccountBar.tsx | 14 +++++++-------
- web/pdf-viewer/components/AccountBar.tsx | 14 +++++++-------
- web/shl/components/AccountBar.tsx | 22 ++++++++++++++++++++++
- web/shl/index.ts | 3 +++
---
## 2026-10-27 13:35 – 13:39 (4m)
**Beschreibung:** Claude Code Session
**Projekt:** nexarch
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- web/dms-browser/components/AccountBar.tsx | 14 +++++++-------
- web/dms-upload/components/AccountBar.tsx | 14 +++++++-------
- web/pdf-viewer/components/AccountBar.tsx | 14 +++++++-------
- web/shl/components/AccountBar.tsx | 22 ++++++++++++++++++++++
- web/shl/index.ts | 3 +++
---
## 2026-10-27 13:42 – 13:52 (10m)
**Beschreibung:** Claude Code Session
**Projekt:** nexarch
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- web/dms-browser/components/AccountBar.tsx | 14 +++++++-------
- web/dms-upload/components/AccountBar.tsx | 14 +++++++-------
- web/pdf-viewer/components/AccountBar.tsx | 14 +++++++-------
- web/shl/components/AccountBar.tsx | 22 ++++++++++++++++++++++
- web/shl/index.ts | 3 +++
---
## 2026-10-27 13:53 – 13:53 (0m)
**Beschreibung:** Claude Code Session
**Projekt:** nexarch
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- web/dms-browser/components/AccountBar.tsx | 14 +++++++-------
- web/dms-upload/components/AccountBar.tsx | 14 +++++++-------
- web/pdf-viewer/components/AccountBar.tsx | 14 +++++++-------
- web/shl/components/AccountBar.tsx | 22 ++++++++++++++++++++++
- web/shl/index.ts | 3 +++
---
## 2026-10-27 13:54 – 14:06 (12m)
**Beschreibung:** Claude Code Session
**Projekt:** nexarch
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- web/dms-browser/components/AccountBar.tsx | 14 +++++++-------
- web/dms-upload/components/AccountBar.tsx | 14 +++++++-------
- web/pdf-viewer/components/AccountBar.tsx | 14 +++++++-------
- web/shl/components/AccountBar.tsx | 22 ++++++++++++++++++++++
- web/shl/index.ts | 3 +++
---
## 2026-10-27 14:08 – 14:41 (32m)
**Beschreibung:** Claude Code Session
**Projekt:** nexarch
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- web/dms-browser/components/AccountBar.tsx | 14 +++++++-------
- web/dms-upload/components/AccountBar.tsx | 14 +++++++-------
- web/pdf-viewer/components/AccountBar.tsx | 14 +++++++-------
- web/shl/components/AccountBar.tsx | 22 ++++++++++++++++++++++
- web/shl/index.ts | 3 +++
---
## 2026-10-27 15:03 – 15:25 (22m)
**Beschreibung:** Claude Code Session
**Projekt:** nexarch
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- web/dms-browser/components/AccountBar.tsx | 14 +++++++-------
- web/dms-upload/components/AccountBar.tsx | 14 +++++++-------
- web/pdf-viewer/components/AccountBar.tsx | 14 +++++++-------
- web/shl/components/AccountBar.tsx | 22 ++++++++++++++++++++++
- web/shl/index.ts | 3 +++
---
## 2026-10-27 15:27 – 15:43 (15m)
**Beschreibung:** Claude Code Session
**Projekt:** nexarch
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- web/dms-browser/components/AccountBar.tsx | 14 +++++++-------
- web/dms-upload/components/AccountBar.tsx | 14 +++++++-------
- web/pdf-viewer/components/AccountBar.tsx | 14 +++++++-------
- web/shl/components/AccountBar.tsx | 22 ++++++++++++++++++++++
- web/shl/index.ts | 3 +++
---
+13
View File
@@ -6,6 +6,17 @@ Stand: 2026-08-26. Zusammenführung aller bisherigen Entscheidungen aus `nexarch
NEXARCH ist die Nachfolgeplattform von **archivmail** (E-Mail-Archivierung) und **archivdms** (Dokumentenmanagement) — nicht als Umbenennung, sondern als modulare Neuentwicklung unter der Positionierung **"Next Generation Information Management"**: E-Mails, Dokumente, digitale Archivierung, OCR, Volltextsuche, Workflows, Compliance, Integrationen, KI — als ein zusammenhängendes System, nicht als zwei getrennte Werkzeuge nebeneinander.
## 1a. Vorbildsysteme
NEXARCH vereint die Stärken bestehender Systeme und vermeidet deren bekannte Schwächen (Details: `dms-kanban/known-issues-archivdms.md`, `dms-kanban/known-issues-archivmail.md`).
| Bereich | Vorbildsysteme |
|---|---|
| Mail | **archivmail** (eigenes Produkt, Vorgänger), **MailPiler** |
| DMS | **paperless-ngx**, **Alfresco**, **Docspell**, **ecoDMS**, **archivdms** (eigenes Produkt, Vorgänger) |
Weitere Vergleichsprojekte (z. B. OpenKM, Nuxeo, Mayan EDMS, Seafile) sind noch zu prüfen. Für das DMS-Modul siehe auch `dms/README.md`.
## 2. Architekturprinzip
**Ein modularer Monolith aus sieben eigenständigen, aber koordinierten Diensten** — kein Microservice-Zwang, aber klare Dienstgrenzen mit eigenem Datenmodell, eigenem Go-Binary, eigenem Kanban-Board:
@@ -64,6 +75,8 @@ Kein Modul kopiert die Daten eines anderen — stattdessen ein wiederkehrendes *
Gemeinsamer Nenner aller drei: **Referenz statt Kopie, Adapter statt Direktkopplung.**
Go-`internal/`-Pakete sind zwischen den Modulen nicht sichtbar; identische Querschnittspakete liegen deshalb im versionierten Modul `common/` (Git-Tags `common/vX.Y.Z`, siehe `common/README.md`). So bleiben Module einzeln baubar (Docker, eigenes Repo, Partner-Build). Fachlich verschiedene Pakete (`audittrail`, `cutoverstage`, `crypto`) bleiben modul-lokal.
Dasselbe Prinzip gilt auch innerhalb eines Moduls, auf Code-Ebene: Hauptdateien/Entry-Points bleiben klein, Fachlogik wandert in eigene Module. Vor einer neuen Funktions-/Modul-Datei wird geprüft, ob dieselbe Funktionalität nicht schon woanders existiert (auch modulübergreifend, z. B. in Core) — keine doppelten Funktionsdateien. Das eigene Kanban-Planungswerkzeug wurde nach genau diesem Muster korrigiert (sieben `kanban.html` enthielten identisches JS/CSS als Volltext-Duplikat, damals in `_shared/kanban.js`/`kanban.css` ausgelagert) — die Boards selbst sind inzwischen komplett aus dem Repo entfernt (Ticket-Historie liegt in der Gitea-PR-Historie), das Prinzip "keine doppelten Funktionsdateien" gilt unverändert für den eigentlichen NEXARCH-Code.
## 6. Technische Architektur (Kurzfassung, Details in eigenen Dokumenten)
+111 -16
View File
@@ -18,17 +18,29 @@ import (
"github.com/jackc/pgx/v5/pgxpool"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/classify"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/confidence"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/correction"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/decisionaudit"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/docchat"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/docchatapi"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/doclang"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/doctype"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/embedpipeline"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/extract"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/extractapi"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/extractconfirmapi"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/extractmapping"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/extractvalidate"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/featureapi"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/highlight"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/invoiceextract"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/metadatapushclient"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/modelconfig"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/moduletype"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/moduletypeapi"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/originmark"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/prompttemplate"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/provider"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/provider/ollama"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/queue"
@@ -39,11 +51,15 @@ import (
"gitea.perlbach24.de/scripte/nexarch/ai/internal/routingsuggestapi"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/semanticsearch"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/semanticsearchapi"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/suggestapi"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/summarize"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/summaryapi"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/tenantregistryclient"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/tenantfeature"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/threadsummarize"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/vectorindex"
"gitea.perlbach24.de/scripte/nexarch/common/auditclient"
"gitea.perlbach24.de/scripte/nexarch/common/policyclient"
"gitea.perlbach24.de/scripte/nexarch/common/tenantregistryclient"
)
const workerPollInterval = 2 * time.Second
@@ -52,13 +68,25 @@ const defaultListenAddr = "127.0.0.1:9600"
const defaultModel = "default"
type config struct {
dsn string
serviceToken string
addr string
model string
moduleURLs map[string]string
coreBaseURL string
coreServiceToken string
dsn string
serviceToken string
addr string
model string
moduleURLs map[string]string
coreBaseURL string
coreServiceToken string
policyURL string
policyServiceToken string
policyDisabled bool
languages *doclang.Registry
}
// policyClient liefert nil nur mit NEXARCH_AI_APP_POLICY_DISABLED=1.
func (c config) policyClient() *policyclient.Client {
if c.policyURL == "" {
return nil
}
return policyclient.New(c.policyURL, c.policyServiceToken)
}
// parseModuleBaseURLs liest "modul=url,modul2=url2" (EXT-05: je Modul eine
@@ -84,13 +112,19 @@ func parseModuleBaseURLs(raw string) map[string]string {
// Programmabbruchs weiter unten im Code.
func loadConfig(getenv func(string) string) (config, error) {
cfg := config{
dsn: getenv("NEXARCH_AI_APP_TENANT_DSN"),
serviceToken: getenv("NEXARCH_AI_APP_SERVICE_TOKEN"),
addr: getenv("NEXARCH_AI_APP_LISTEN_ADDR"),
model: getenv("NEXARCH_AI_APP_MODEL"),
moduleURLs: parseModuleBaseURLs(getenv("NEXARCH_AI_APP_MODULE_BASE_URLS")),
coreBaseURL: getenv("NEXARCH_AI_APP_CORE_BASE_URL"),
coreServiceToken: getenv("NEXARCH_AI_APP_CORE_SERVICE_TOKEN"),
dsn: getenv("NEXARCH_AI_APP_TENANT_DSN"),
serviceToken: getenv("NEXARCH_AI_APP_SERVICE_TOKEN"),
addr: getenv("NEXARCH_AI_APP_LISTEN_ADDR"),
model: getenv("NEXARCH_AI_APP_MODEL"),
moduleURLs: parseModuleBaseURLs(getenv("NEXARCH_AI_APP_MODULE_BASE_URLS")),
coreBaseURL: getenv("NEXARCH_AI_APP_CORE_BASE_URL"),
coreServiceToken: getenv("NEXARCH_AI_APP_CORE_SERVICE_TOKEN"),
policyURL: getenv("NEXARCH_AI_APP_POLICY_URL"),
policyServiceToken: getenv("NEXARCH_AI_APP_POLICY_SERVICE_TOKEN"),
policyDisabled: getenv("NEXARCH_AI_APP_POLICY_DISABLED") == "1",
}
if cfg.policyURL != "" && cfg.policyServiceToken == "" {
return config{}, errors.New("ai-app: NEXARCH_AI_APP_POLICY_SERVICE_TOKEN ist erforderlich, wenn NEXARCH_AI_APP_POLICY_URL gesetzt ist")
}
if cfg.dsn == "" {
return config{}, errors.New("ai-app: NEXARCH_AI_APP_TENANT_DSN ist erforderlich")
@@ -98,6 +132,14 @@ func loadConfig(getenv func(string) string) (config, error) {
if cfg.serviceToken == "" {
return config{}, errors.New("ai-app: NEXARCH_AI_APP_SERVICE_TOKEN ist erforderlich")
}
if cfg.policyURL == "" && !cfg.policyDisabled {
return config{}, errors.New("ai-app: NEXARCH_AI_APP_POLICY_URL ist erforderlich (oder NEXARCH_AI_APP_POLICY_DISABLED=1 als Uebergang)")
}
languages, err := doclang.LoadRegistryFile(getenv(doclang.EnvLanguagesFile))
if err != nil {
return config{}, err
}
cfg.languages = languages
if cfg.addr == "" {
cfg.addr = defaultListenAddr
}
@@ -129,7 +171,11 @@ func build(pool *pgxpool.Pool, cfg config, prov provider.Provider, workerID stri
).RegisterRoutes(mux)
searchSvc := semanticsearch.New(prov, vectorindex.New(pool), cfg.model)
semanticsearchapi.NewServer(searchSvc, cfg.serviceToken).RegisterRoutes(mux)
policy := cfg.policyClient()
if policy == nil {
log.Print("ai-app: WARNUNG: Rechtepruefung ai.search.read deaktiviert (NEXARCH_AI_APP_POLICY_DISABLED=1)")
}
semanticsearchapi.NewServer(searchSvc, cfg.serviceToken).WithPolicy(policy).RegisterRoutes(mux)
chunkStore := vectorindex.New(pool)
docchatapi.NewServer(
@@ -168,12 +214,61 @@ func build(pool *pgxpool.Pool, cfg config, prov provider.Provider, workerID stri
log.Print("ai-app: NEXARCH_AI_APP_CORE_BASE_URL nicht gesetzt, Objekttyp-Registrierung (GOV-01) deaktiviert")
}
registry := cfg.languages
if registry == nil {
registry = doclang.DefaultRegistry()
}
langStore := &doclang.Store{Pool: pool}
markerStore := &originmark.Store{Pool: pool}
suggest := &suggestapi.Server{
Provider: prov,
Model: cfg.model,
Resolver: &doclang.Resolver{Registry: registry, Tenants: langStore},
Languages: langStore,
Markers: markerStore,
ServiceToken: cfg.serviceToken,
}
// CLS-09: ohne Core-Audit gibt es keine Uebernahme-Protokollierung.
if cfg.coreBaseURL != "" {
suggest.Adopt = &originmark.Service{
Markers: markerStore,
Audit: decisionaudit.NewRecorder(auditclient.New(cfg.coreBaseURL, cfg.coreServiceToken)),
}
} else {
log.Print("ai-app: NEXARCH_AI_APP_CORE_BASE_URL nicht gesetzt, Uebernahme-Protokollierung (/suggest/adopt) deaktiviert")
}
suggest.RegisterRoutes(mux)
quotaStore := quota.NewStore(pool, nil)
q := queue.NewQueue(pool, 5*time.Minute)
q.SetQuotaEnforcer(quotaStore)
worker := queue.NewWorker(q, prov, workerID)
worker.SetUsageRecorder(quotaStore)
taxonomy := &doctype.Taxonomy{Pool: pool}
listers := map[string]modelconfig.ModelLister{}
if l, ok := prov.(modelconfig.ModelLister); ok {
listers["ollama"] = l
}
classifier := &classify.Classifier{Provider: prov, Model: cfg.model}
features := &featureapi.Server{
Classifier: classifier,
Doctype: &doctype.Detector{Classifier: classifier, Taxonomy: taxonomy},
Highlight: &highlight.Detector{Provider: prov, Model: cfg.model},
Taxonomy: taxonomy,
Features: &tenantfeature.Store{Pool: pool},
Thresholds: &confidence.Store{Pool: pool},
Corrections: &correction.Recorder{Pool: pool},
Embeddings: embedpipeline.New(pool, q, cfg.model),
Models: modelconfig.NewStore(pool, listers),
Templates: prompttemplate.New(pool),
ServiceToken: cfg.serviceToken,
}
if cfg.coreBaseURL != "" {
features.Audit = decisionaudit.NewRecorder(auditclient.New(cfg.coreBaseURL, cfg.coreServiceToken))
}
features.RegisterRoutes(mux)
return wiring{mux: mux, queue: q, worker: worker, quotaStore: quotaStore}
}
+165 -2
View File
@@ -2,6 +2,7 @@ package main
import (
"context"
"encoding/json"
"errors"
"net/http"
"net/http/httptest"
@@ -17,7 +18,7 @@ import (
"gitea.perlbach24.de/scripte/nexarch/ai/internal/provider"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/provider/mock"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/queue"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/testdb"
"gitea.perlbach24.de/scripte/nexarch/common/testdb"
)
// AK3 + AK4: eine fehlende Pflicht-Umgebungsvariable liefert einen klaren
@@ -44,10 +45,36 @@ func TestLoadConfig_MissingRequiredVars(t *testing.T) {
}
}
func TestLoadConfig_Defaults(t *testing.T) {
// Issue #369: Policy-URL ohne Service-Token darf nicht still ohne
// Rechtepruefung starten.
func TestLoadConfig_PolicyURLRequiresServiceToken(t *testing.T) {
env := map[string]string{
"NEXARCH_AI_APP_TENANT_DSN": "postgres://x",
"NEXARCH_AI_APP_SERVICE_TOKEN": "tok",
"NEXARCH_AI_APP_POLICY_URL": "http://127.0.0.1:8093",
}
get := func(k string) string { return env[k] }
if _, err := loadConfig(get); err == nil || !strings.Contains(err.Error(), "NEXARCH_AI_APP_POLICY_SERVICE_TOKEN") {
t.Fatalf("erwartete fehler zur fehlenden NEXARCH_AI_APP_POLICY_SERVICE_TOKEN, bekam %v", err)
}
env["NEXARCH_AI_APP_POLICY_SERVICE_TOKEN"] = "policy-tok"
cfg, err := loadConfig(get)
if err != nil {
t.Fatalf("unerwarteter fehler: %v", err)
}
if cfg.policyClient() == nil {
t.Fatal("policy-client muss bei gesetzter URL aktiv sein")
}
if (config{}).policyClient() != nil {
t.Fatal("ohne URL darf kein policy-client entstehen")
}
}
func TestLoadConfig_Defaults(t *testing.T) {
env := map[string]string{
"NEXARCH_AI_APP_TENANT_DSN": "postgres://x",
"NEXARCH_AI_APP_SERVICE_TOKEN": "tok",
"NEXARCH_AI_APP_POLICY_DISABLED": "1",
}
cfg, err := loadConfig(func(k string) string { return env[k] })
if err != nil {
@@ -61,6 +88,96 @@ func TestLoadConfig_Defaults(t *testing.T) {
}
}
func TestBuild_FeatureAPIAgainstRealTables(t *testing.T) {
pool := newTestPool(t)
prov := &mock.Provider{ChatResponse: provider.ChatResponse{Message: provider.Message{Role: provider.RoleAssistant, Content: `{"category":"rechnung","confidence":0.9,"reasoning":"x"}`}}}
w := build(pool, config{serviceToken: "tok", model: "mock"}, prov, "feature-worker")
srv := httptest.NewServer(w.mux)
defer srv.Close()
call := func(method, path, body string) (int, map[string]any) {
t.Helper()
req, _ := http.NewRequest(method, srv.URL+path, strings.NewReader(body))
req.Header.Set("X-Service-Token", "tok")
resp, err := http.DefaultClient.Do(req)
if err != nil {
t.Fatalf("%s %s: %v", method, path, err)
}
defer resp.Body.Close()
var out map[string]any
_ = json.NewDecoder(resp.Body).Decode(&out)
return resp.StatusCode, out
}
want := func(code int, got int, what string) {
t.Helper()
if got != code {
t.Errorf("%s: status %d, want %d", what, got, code)
}
}
code, _ := call("POST", "/tenants/featt/doctypes/defaults", `{}`)
want(200, code, "doctype defaults")
code, out := call("POST", "/doctype/detect", `{"tenant_slug":"featt","text":"Rechnung Nr. 5"}`)
want(200, code, "doctype detect")
if out["doc_type"] != "rechnung" {
t.Errorf("doc_type = %v", out["doc_type"])
}
code, out = call("POST", "/classify", `{"tenant_slug":"featt","categories":["rechnung","vertrag"],"text":"x"}`)
want(200, code, "classify")
if out["meets_threshold"] != true {
t.Errorf("0.9 gegen default 0.7: meets_threshold = %v", out["meets_threshold"])
}
code, _ = call("PUT", "/tenants/featt/confidence/classify", `{"threshold":0.95}`)
want(200, code, "confidence put")
_, out = call("POST", "/classify", `{"tenant_slug":"featt","categories":["rechnung","vertrag"],"text":"x"}`)
if out["meets_threshold"] != false {
t.Errorf("0.9 gegen 0.95: meets_threshold = %v", out["meets_threshold"])
}
code, _ = call("PUT", "/tenants/featt/features/classify", `{"enabled":false}`)
want(200, code, "feature off")
code, _ = call("POST", "/classify", `{"tenant_slug":"featt","categories":["rechnung"],"text":"x"}`)
want(403, code, "classify deaktiviert")
code, _ = call("POST", "/classify", `{"tenant_slug":"featt2","categories":["rechnung"],"text":"x"}`)
want(200, code, "classify anderer mandant")
code, _ = call("POST", "/corrections", `{"tenant_slug":"featt","function":"classify","input_ref":"d1","suggested_category":"rechnung","suggested_confidence":0.9,"corrected_category":"vertrag","actor":"u1"}`)
want(204, code, "correction record")
_, out = call("GET", "/tenants/featt/corrections/classify/stats", "")
if out["total_corrections"] != 1.0 {
t.Errorf("total_corrections = %v", out["total_corrections"])
}
code, _ = call("POST", "/prompt-templates/featt-key", `{"body":"eins","created_by":"u"}`)
want(201, code, "template save")
code, out = call("GET", "/prompt-templates/featt-key", "")
want(200, code, "template active")
if out["body"] != "eins" {
t.Errorf("body = %v", out["body"])
}
code, out = call("POST", "/embeddings/ingest", `{"tenant_slug":"featt","source_type":"doc","source_id":"1","text":"Ein kurzer Text."}`)
want(202, code, "embedding ingest")
code, out = call("GET", "/embeddings/status?tenant_slug=featt&source_type=doc&source_id=1", "")
want(200, code, "embedding status")
if cs, _ := out["chunks"].([]any); len(cs) != 1 {
t.Errorf("chunks = %v", out["chunks"])
}
code, _ = call("GET", "/tenants/featt/model-config/chat", "")
want(404, code, "model-config ungesetzt")
code, _ = call("PUT", "/tenants/featt/model-config/chat", `{"provider":"ollama","model":"x"}`)
want(400, code, "model-config ohne lister")
code, _ = call("POST", "/summary-quality/evaluate", `{"samples":[{"type":"t","source":"a b c","summary":"a b"}]}`)
want(200, code, "summary-quality")
code, _ = call("POST", "/highlight", `{"tenant_slug":"featt","text":"Frist endet morgen."}`)
if code == http.StatusNotFound || code == http.StatusUnauthorized {
t.Errorf("highlight nicht registriert: %d", code)
}
}
func newTestPool(t *testing.T) *pgxpool.Pool {
t.Helper()
dsn := os.Getenv("TEST_TENANT_DSN")
@@ -139,6 +256,20 @@ func TestBuild_AllPackagesRegistered(t *testing.T) {
if resp := get("POST", "/invoices/extract", `{"text":""}`); resp.StatusCode == http.StatusNotFound {
t.Errorf("extractapi route nicht registriert (404)")
}
// suggestapi (CLS-06..09): Mandantenkonfiguration laeuft gegen die echten Tabellen.
if resp := get("PUT", "/tenants/acme/language", `{"language":"en"}`); resp.StatusCode != http.StatusOK {
t.Errorf("suggestapi language status = %d, want 200", resp.StatusCode)
}
if resp := get("PUT", "/tenants/acme/origin-marker", `{"marker":"ki"}`); resp.StatusCode != http.StatusOK {
t.Errorf("suggestapi origin-marker status = %d, want 200", resp.StatusCode)
}
if resp := get("POST", "/suggest/title", `{"tenant_slug":"acme","text":""}`); resp.StatusCode != http.StatusBadRequest {
t.Errorf("suggestapi title status = %d, want 400", resp.StatusCode)
}
if resp := get("POST", "/suggest/adopt", `{}`); resp.StatusCode != http.StatusServiceUnavailable {
t.Errorf("suggestapi adopt ohne Core-URL status = %d, want 503", resp.StatusCode)
}
}
// AK2 + Pruefung 2: ein echter Inferenz-Auftrag durchlaeuft die Warteschlange
@@ -184,3 +315,35 @@ func TestBuild_QueueWorkerProcessesJobWithQuota(t *testing.T) {
t.Errorf("quota-zaehlerstand = %d, want 1 (GOV-04-Verbrauchszaehlung nicht verdrahtet)", value)
}
}
func TestLoadConfig_PolicyRequiredUnlessDisabled(t *testing.T) {
env := map[string]string{
"NEXARCH_AI_APP_TENANT_DSN": "postgres://x",
"NEXARCH_AI_APP_SERVICE_TOKEN": "tok",
}
get := func(k string) string { return env[k] }
if _, err := loadConfig(get); err == nil || !strings.Contains(err.Error(), "NEXARCH_AI_APP_POLICY_URL") {
t.Fatalf("erwartete fehler zur fehlenden NEXARCH_AI_APP_POLICY_URL, bekam %v", err)
}
env["NEXARCH_AI_APP_POLICY_DISABLED"] = "1"
if _, err := loadConfig(get); err != nil {
t.Fatalf("mit POLICY_DISABLED=1 darf der Start nicht scheitern: %v", err)
}
}
func TestLoadConfig_LanguagesFile(t *testing.T) {
env := map[string]string{
"NEXARCH_AI_APP_TENANT_DSN": "postgres://x",
"NEXARCH_AI_APP_SERVICE_TOKEN": "tok",
"NEXARCH_AI_APP_POLICY_DISABLED": "1",
}
get := func(k string) string { return env[k] }
cfg, err := loadConfig(get)
if err != nil || cfg.languages == nil {
t.Fatalf("standardsprachen erwartet: %v", err)
}
env["NEXARCH_AI_LANGUAGES_FILE"] = filepath.Join(t.TempDir(), "fehlt.json")
if _, err := loadConfig(get); err == nil || !strings.Contains(err.Error(), "NEXARCH_AI_LANGUAGES_FILE") {
t.Fatalf("klarer fehler bei fehlender sprachdatei erwartet, bekam %v", err)
}
}
+2 -1
View File
@@ -19,7 +19,7 @@ import (
)
const (
fixtureTenant = "beispiel-mandant"
fixtureTenant = "beispiel-mandant"
fixtureSourceType = "dms_document"
fixtureSourceID = "beispiel-dokument-1"
)
@@ -77,5 +77,6 @@ func main() {
docchatapi.NewServer(chatSvc, "test-token").RegisterRoutes(mux)
log.Println("searchchatapi-mockserver listening on 127.0.0.1:19603")
// archtest:allow Einmaliges lokales pa11y-Testwerkzeug, kein Produktdienst
log.Fatal(http.ListenAndServe("127.0.0.1:19603", mux))
}
+30
View File
@@ -0,0 +1,30 @@
# CLS-06 – Automatischer Titelvorschlag – Prüfprotokoll
Datum: 2026-11-06. Host: Entwicklungshost 192.168.1.204. Externer Ollama-Server `office.sysops.de:11435` (Text), Modell `gemma4:26b` (`gpt-oss:20b` aus CLS-03 ist dort nicht mehr verfügbar). Ausschließlich synthetische Dokumente.
## Umsetzung
Neues Paket `ai/internal/titlesuggest`: `Suggester.Suggest(ctx, moduleContext, language, documentText)` liefert bis zu 3 `Suggestion{Title, Confidence}`. Baut auf `provider.Provider` (ENG-01) auf, ohne Schreibpfad. Titel werden von Leerraum bereinigt, auf 120 Zeichen gekürzt und case-unabhängig dedupliziert. Kaputte Modellantwort ergibt eine leere Liste.
## Akzeptanzkriterien
| # | Kriterium | Ergebnis |
|---|---|---|
| 1 | Bis zu drei Titelvorschläge mit Konfidenz | **Erfüllt.** `TestSuggest_ReturnsConfidencePerTitle`, Konfidenz auf [0,1] geklammert. |
| 2 | Titel bereinigt, gekürzt, ohne Dopplung | **Erfüllt.** `TestSuggest_DedupTrimAndLimit`. |
| 3 | Vorschlag ohne automatische Übernahme | **Erfüllt.** Strukturell: kein Pool, kein Client außer `provider.Provider`. |
| 4 | Fehlerhafte Modellantwort ergibt leere Liste statt Fehler | **Erfüllt.** `TestSuggest_GarbageYieldsEmpty`; Providerfehler und fehlender Provider werden als Fehler gemeldet (`TestSuggest_ProviderErrorAndNil`). |
## Prüfungen vor der Abnahme
| # | Prüfung | Zielwert | Ergebnis |
|---|---|---|---|
| 1 | Akzeptanzkriterien mit Mock-Provider belegt | alle 4 | **Bestanden.** 4/4 Unit-Tests grün. |
| 2 | Trefferquote an synthetischem Testset gegen echtes Modell | dokumentiert | **Bestanden, 5/5.** `TestSuggest_RealInference_Accuracy`: Rechnung, Vertrag, Lieferschein, Zahlungserinnerung, englische Rechnung; jeweils enthielt mindestens ein Titel den erwarteten Begriff. Laufzeit 54 s. |
| 3 | Keine automatische Übernahme geprüft | strukturell | **Bestanden.** Siehe AK3. |
## Einschränkungen
- Testset mit 5 Fällen; "Treffer" heißt: ein erwarteter Begriff kommt im Titel vor, nicht, dass der Titel sprachlich optimal ist.
- Keine HTTP-API und keine Verdrahtung in `cmd/ai-app` (nicht Teil der Kriterien).
- Der Parameter `language` ist ein reiner Prompt-Hinweis; die Spracherkennung liefert CLS-08.
+33
View File
@@ -0,0 +1,33 @@
# CLS-07 – Automatischer Korrespondenten-Vorschlag – Prüfprotokoll
Datum: 2026-11-06. Host: Entwicklungshost 192.168.1.204. Externer Ollama-Server `office.sysops.de:11435` (Text), Modell `gemma4:26b`. Ausschließlich synthetische Dokumente, keine echten Kunden- oder Mandantendaten.
## Umsetzung
Neues Paket `ai/internal/correspondentsuggest`: `Suggester.Suggest(ctx, existing, moduleContext, documentText)` liefert `(Suggestion{Name, Confidence, Existing}, ok, error)`. Ein Treffer auf einen bekannten Korrespondenten (case- und leerraumunabhängig) übernimmt dessen exakte Schreibweise und setzt `Existing=true`. Ohne erkennbaren Korrespondenten ist `ok=false`. Kein Schreibpfad.
## Akzeptanzkriterien
| # | Kriterium | Ergebnis |
|---|---|---|
| 1 | Korrespondent mit Konfidenz | **Erfüllt.** `TestSuggest_NormalizesToExisting`, Konfidenz geklammert (`TestSuggest_NewCorrespondentNotMarkedExisting`). |
| 2 | Bekannter Korrespondent übernimmt exakte Schreibweise, als bestehend markiert | **Erfüllt.** `TestSuggest_NormalizesToExisting`. |
| 3 | Kein erkennbarer Korrespondent ergibt keinen Vorschlag | **Erfüllt.** `TestSuggest_NoneOrGarbage` (leerer Name, Müll, abgeschnittenes JSON). |
| 4 | Vorschlag ohne automatische Übernahme | **Erfüllt.** Strukturell, kein Schreibpfad. |
## Prüfungen vor der Abnahme
| # | Prüfung | Zielwert | Ergebnis |
|---|---|---|---|
| 1 | Akzeptanzkriterien mit Mock-Provider belegt | alle 4 | **Bestanden.** 4/4 Unit-Tests grün. |
| 2 | Trefferquote an synthetischem Testset gegen echtes Modell | dokumentiert | **Bestanden, 4/4** im zweiten Lauf (Fälle: 3 bekannte Korrespondenten, 1 neuer). Laufzeit 66 s. |
| 3 | Keine echten Kunden-/Mandantendaten im Testset | keine | **Bestanden.** Nur Muster-/Beispiel-Namen. |
## Befund und Korrektur
Erster Realtest-Lauf: 3/4. Bei unbekanntem Absender ("Neuer Absender KG") lieferte das Modell keinen Namen, weil der Prompt "bevorzugt exakt einen davon wählen" zu stark auf die Liste festlegte. Prompt geändert auf "wähle exakt einen davon, wenn er der Absender ist; ist der Absender keiner davon, nenne ihn so, wie er im Dokument steht". Zweiter Lauf: 4/4. Unit-Tests blieben grün.
## Einschränkungen
- Testset mit 4 Fällen, ein Fall für den Pfad "neuer Korrespondent".
- Anlage neuer Korrespondenten im DMS, API und Verdrahtung in `cmd/ai-app` sind nicht Teil der Kachel.
+34
View File
@@ -0,0 +1,34 @@
# CLS-08 – Mehrsprachige Dokumentanalyse – Prüfprotokoll
Datum: 2026-11-06. Host: Entwicklungshost 192.168.1.204 (Wegwerf-Rolle/DB `cls08_test`, nach Abschluss entfernt).
## Umsetzung
- Neues Paket `ai/internal/doclang`: `Registry` mit Stoppwortlisten je Sprache (Deutsch, Englisch eingebaut), `Detect(text)` und `Resolver.Resolve(ctx, tenant, explicitDocLang, text)`.
- Weitere Sprachen ohne Codeänderung: JSON-Datei über `NEXARCH_AI_LANGUAGES_FILE` (`[{"code","name","stopwords"}]`).
- Ausgabesprache je Mandant in Tabelle `ai_tenant_languages` (Migration `0013`), bei jedem Aufruf live gelesen.
- `tagsuggest.Suggester.Language` nimmt die Ausgabesprache in den Prompt auf; bestehende Tags anderer Sprache behalten Vorrang vor neuen. `titlesuggest` übernahm die Sprache bereits als Parameter (CLS-06).
## Akzeptanzkriterien
| # | Kriterium | Ergebnis |
|---|---|---|
| 1 | Dokumentsprache erkannt oder per Parameter vorgegeben | **Erfüllt.** `TestDetect_GermanAndEnglish`, `TestResolve_ExplicitOverridesDetectionAndUnknownFails` (unbekannter Code ergibt `ErrUnknownLanguage`). Unklare Texte ergeben keine Sprache statt einer Fehlzuordnung (`TestDetect_UnclearYieldsNothing`). |
| 2 | Ausgabesprache je Mandant konfigurierbar | **Erfüllt.** `TestStore_PerTenantAndLive` (echte DB, Mandantentrennung), `TestResolve_OutputFollowsTenantOtherwiseDocument`. |
| 3 | Bestehende Tag-Taxonomie wird bei abweichender Dokumentsprache respektiert | **Teilweise belegt.** `TestSuggest_LanguageInPromptAndTaxonomyKept`: Prompt enthält Sprachvorgabe und Vorrang bestehender Tags; englisches Dokument gegen deutschen Tag ergibt die bestehende Schreibweise. Mit Stub-Provider, nicht mit echtem Modell. |
| 4 | Deutsch und Englisch belegt, weitere Sprachen per Konfiguration | **Erfüllt.** `TestRegistry_ExtensibleByJSONWithoutCodeChange` (Französisch per JSON), `TestRegistryFromEnv`, `TestLoadJSON_RejectsBadCode`. |
## Prüfungen vor der Abnahme
| # | Prüfung | Ergebnis |
|---|---|---|
| 1 | Testset deutsch/englisch gemessen | **Bestanden, 20/20** (10 de, 10 en), Zielwert 90 %. `TestDetect_SyntheticTestsetAccuracy`. |
| 2 | Wechsel der Ausgabesprache wirkt ohne Neustart | **Bestanden.** In `TestStore_PerTenantAndLive` wird die Sprache gewechselt und sofort neu gelesen. |
| 3 | Regressionstest bestehender CLS-Funktionen | **Bestanden.** Alle bestehenden `tagsuggest`-Tests grün (Unit; die zwei Realtests wurden ohne `TEST_OLLAMA_*` übersprungen). Neues Feld `Language` ist optional, leer ergibt unveränderten Prompt (`TestSuggest_NoLanguageNoInstruction`). |
## Einschränkungen
- Das Testset hat derselbe Autor wie die Stoppwortlisten geschrieben; 20/20 ist daher optimistisch. Kurze Texte, Texte mit vielen Zahlen/Namen und gemischtsprachige Dokumente sind nicht gemessen und liefern bei zu wenigen Treffern keine Sprache.
- AK3 ist nur mit Stub-Provider belegt, nicht gegen ein echtes Modell.
- `classify`, `extract` und `correspondentsuggest` nutzen die Sprache noch nicht; keine Verdrahtung in `cmd/ai-app` und kein API-Endpunkt zum Setzen der Mandantensprache.
- Keine Übersetzung von Dokumentinhalten (ausdrücklich nicht Teil der Kachel).
+32
View File
@@ -0,0 +1,32 @@
# CLS-09 – Kennzeichnung KI-gesetzter Zuordnungen – Prüfprotokoll
Datum: 2026-11-06. Host: Entwicklungshost 192.168.1.204 (Wegwerf-Rolle/DB `cls09_test`, nach Abschluss entfernt).
## Umsetzung
- Migration `0012_ai_origin_marker`: Tabelle `ai_origin_markers` (eine Markierung je Mandant, nicht leer).
- Paket `ai/internal/originmark`: `Store` (Set/Get/Clear, Validierung 1 bis 64 Zeichen ohne Steuerzeichen) und `Service.Apply(ctx, Adoption)`.
- `Apply` liefert die Markierung, die der Aufrufer an die übernommene Zuordnung hängt. Nur bei `Source=ai`; manuelle Zuordnungen bleiben unmarkiert und unprotokolliert. KI-Übernahmen gehen über `decisionaudit.Recorder` ins zentrale Audit, mit `origin_marker` im Ergebnis.
## Akzeptanzkriterien
| # | Kriterium | Ergebnis |
|---|---|---|
| 1 | Übernommene KI-Vorschläge tragen eine mandantenweit konfigurierbare Markierung | **Erfüllt auf Paketebene.** `TestApply_AIAdoptionGetsMarkerAndAudit`, `TestStore_PerTenantSetGetClear` (echte DB). Der Aufrufer (DMS/Mail) hängt die Markierung an; diese Anbindung existiert noch nicht. |
| 2 | Manuelle Zuordnungen werden nicht markiert | **Erfüllt.** `TestApply_ManualNeverMarkedNorAudited`. |
| 3 | Markierung im Audit der KI-Entscheidung nachvollziehbar | **Teilweise belegt.** Mit Fake-Recorder: `origin_marker`, `kind` und `value` stehen im Ergebnis. Echter Core-Audit (`auditclient`) nicht getestet. |
| 4 | Ohne Konfiguration keine Markierung | **Erfüllt.** `TestApply_NoConfigNoMarkerButAudited`; die KI-Übernahme wird trotzdem protokolliert. |
## Prüfungen vor der Abnahme
| # | Prüfung | Ergebnis |
|---|---|---|
| 1 | Übernahme setzt die Markierung nachweislich, manuelle Zuordnung nicht | **Bestanden** (Unit). Ende-zu-Ende mit DMS: **offen**. |
| 2 | Markierung erscheint im Entscheidungs-Audit | **Teilweise.** Siehe AK3. |
| 3 | Mandantentrennung | **Bestanden.** `TestApply_TenantIsolation` und DB-Test (`acme`/`other`, Clear berührt `other` nicht). |
## Einschränkungen
- Kein API-Endpunkt zum Setzen der Markierung, keine Verdrahtung in `cmd/ai-app`, keine Anbindung an DMS/Mail.
- Schlägt das Audit fehl, liefert `Apply` einen Fehler und keine Markierung; ob die Fachaktion dann trotzdem Bestand hat, entscheidet der Aufrufer.
- Oberfläche zur Filterung nach Markierung ist nicht Teil der Kachel.
+32
View File
@@ -0,0 +1,32 @@
# ENG-09 – Weitere Provider (OpenAI-kompatible Presets, Gemini) – Prüfprotokoll
Datum: 2026-11-06. Host: Entwicklungshost 192.168.1.204.
## Umsetzung
- Neues Paket `ai/internal/provider/gemini`: Chat, Completion (über Chat) und Embedding gegen die Gemini-REST-API (`generateContent`, `embedContent`). Schlüssel aus `NEXARCH_AI_GEMINI_API_KEY`, optionale Basis-URL `NEXARCH_AI_GEMINI_BASE_URL`; Schlüssel nur im Header `x-goog-api-key`. Ohne Schlüssel liefert `NewFromEnv` `ErrDisabled`.
- OpenAI-kompatible Dienste: der vorhandene `openai`-Adapter nimmt bereits eine Basis-URL (`NEXARCH_AI_OPENAI_BASE_URL`); es war keine Codeänderung nötig.
- Opt-in je Mandant: `openai.GuardedProvider` mit `TenantGate` umschließt jeden `provider.Provider`, auch den Gemini-Adapter.
## Akzeptanzkriterien
| # | Kriterium | Ergebnis |
|---|---|---|
| 1 | OpenAI-kompatible Dienste ohne Codeänderung über Basis-URL und Schlüssel aus Umgebungsvariablen | **Teilweise belegt.** Mechanismus vorhanden und durch bestehende `openai`-Tests (Fake-Server mit abweichender Basis-URL) abgedeckt. Gegen OpenRouter, vLLM, LiteLLM oder DeepSeek nicht getestet (keine Zugänge). |
| 2 | Gemini-Adapter erfüllt den Provider-Vertragstest | **Erfüllt gegen Fake-Server.** `TestContract_Gemini` (Chat, Complete, Embed über `runContract`), `TestUniformErrors` (404, 429, 500 auf die einheitlichen Codes), Timeout/Nicht-Erreichbar (`TestUnreachableAndCancelled`). Nicht gegen die echte Gemini-API. |
| 3 | Externe Anbieter nur nach Opt-in und Residenz-Prüfung | **Teilweise belegt.** Opt-in per `GuardedProvider` belegt (`TestOptIn_GuardedProviderBlocksWithoutCallingServer`: kein Aufruf ohne Freigabe oder ohne Mandant). Datenresidenz-Prüfung (`residency`) ist nicht an den Adapter gekoppelt. |
| 4 | Zugangsdaten ausschließlich über Umgebungsvariablen | **Erfüllt.** `TestDisabledWithoutKey`; `TestKeyNotInErrors`; Schlüssel nie in der URL (`fakeServer` prüft den Query-Parameter `key`). |
## Prüfungen vor der Abnahme
| # | Prüfung | Ergebnis |
|---|---|---|
| 1 | Vertragstest gegen jeden neuen Adapter | **Bestanden** (Fake-Server). Echte Gemini-API: **offen**, kein API-Schlüssel vorhanden. |
| 2 | Opt-in-Verweigerung blockiert den Aufruf nachweislich | **Bestanden.** Server-Zähler bleibt unberührt. |
| 3 | Keine echten Kundendaten in Tests gegen externe Dienste | **Bestanden.** Es gibt keine Tests gegen externe Dienste. |
## Einschränkungen
- Kein Lauf gegen eine echte Gemini-API; Request-/Response-Format nach der öffentlichen REST-Dokumentation nachgebaut, nicht gegen den echten Dienst verifiziert.
- `ai-app` bindet den Adapter nicht ein; die Fehlermeldung von `GuardedProvider` nennt den Anbieter `openai`, auch wenn Gemini umschlossen ist.
- Kopplung an `residency` und Streaming sind nicht Teil dieser Kachel.
+27
View File
@@ -0,0 +1,27 @@
# ai-app: Routen aus `featureapi`
Alle Routen verlangen `X-Service-Token`. Verdrahtet in `ai/cmd/ai-app/main.go` (`build`), Tests in `ai/internal/featureapi` und `TestBuild_FeatureAPIAgainstRealTables`.
| Route | Paket |
|---|---|
| `POST /classify` | classify (mit Audit, wenn Core konfiguriert und `actor` + `input_ref` gesetzt) |
| `POST /doctype/detect`, `GET`/`POST /tenants/{t}/doctypes`, `POST /tenants/{t}/doctypes/defaults` | doctype |
| `POST /highlight` | highlight |
| `GET`/`PUT /tenants/{t}/features/{function}` | tenantfeature |
| `GET`/`PUT /tenants/{t}/confidence/{function}` | confidence |
| `GET`/`PUT /tenants/{t}/model-config/{function}` | modelconfig |
| `POST /corrections`, `GET /tenants/{t}/corrections/{function}/stats` | correction |
| `POST /embeddings/ingest`, `GET /embeddings/status` | embedpipeline |
| `POST`/`GET /prompt-templates/{key}`, `GET .../history`, `PUT .../active` | prompttemplate |
| `POST /summary-quality/evaluate` | summaryquality |
## Verhalten
- `classify`, `doctype`, `highlight` prüfen den Mandantenschalter (`tenantfeature`, Funktionsnamen `classify`, `doctype`, `highlight`). Deaktiviert ergibt 403. Der globale Core-Schalter (LIC-02) ist nicht angebunden und gilt als an.
- `classify` und `doctype` liefern `meets_threshold` gegen die Schwelle aus `confidence` (Standard 0,7). Die Schwelle entscheidet nur die Anzeige, nichts wird automatisch übernommen.
- Embeddings: `ingest` reiht nur ein. Verarbeitet werden die Aufträge vom vorhandenen Queue-Worker.
## Nicht angebunden
- `modelconfig` und `prompttemplate` speichern nur. Kein Inferenzpfad liest die Mandanten-Modellwahl oder die aktive Vorlage; Suggest-, Summary- und Extract-Routen nutzen weiter `NEXARCH_AI_APP_MODEL` und die festen Prompts. `modelconfig` kennt nur den Provider `ollama`.
- `notraining` ist ein reiner Architekturtest, `refcorpus` ein Testkorpus. Beide haben keine Laufzeitfunktion und bekommen keine Route.
+4 -1
View File
@@ -4,7 +4,10 @@ go 1.25.0
toolchain go1.25.13
require github.com/jackc/pgx/v5 v5.11.0
require (
gitea.perlbach24.de/scripte/nexarch/common v0.2.0
github.com/jackc/pgx/v5 v5.11.0
)
require (
github.com/jackc/pgpassfile v1.0.0 // indirect
+2
View File
@@ -1,3 +1,5 @@
gitea.perlbach24.de/scripte/nexarch/common v0.2.0 h1:gcOjoOvUkT0Zmqq3m7WQp1MfXSgmzrGTF2E/+NkO2bw=
gitea.perlbach24.de/scripte/nexarch/common v0.2.0/go.mod h1:CiEImG++QMt1xZqSMeDJhPiB7sfWTLZOwjS6OY/Iguc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-72
View File
@@ -1,72 +0,0 @@
// Package auditclient ist ein schlanker HTTP-Client fuer Core AUD-08
// (internal/audit, POST /audit/ingest). AI ist ein physisch getrenntes
// Go-Modul und kann Cores internal/audit daher nicht direkt importieren -
// nur ueber dessen HTTP-Schnittstelle. Gleiches Muster wie
// connect/internal/auditclient (ENG-06) und archive/internal/auditclient.
package auditclient
import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/httpclient"
)
type Client struct {
BaseURL string
ServiceToken string
HTTPClient *http.Client
}
func New(baseURL, serviceToken string) *Client {
return &Client{BaseURL: baseURL, ServiceToken: serviceToken, HTTPClient: httpclient.Default()}
}
type ingestRequest struct {
TenantSlug string `json:"tenant_slug"`
Actor string `json:"actor"`
Action string `json:"action"`
Target string `json:"target"`
Metadata map[string]any `json:"metadata"`
}
// Ingest meldet EIN Audit-Ereignis an Core. Absichtlich keine
// Update-/Delete-Methode: der Ingest-Endpunkt (AUD-08) ruft denselben
// alleinigen, anfuegenden Schreibpfad wie core-interne Aufrufer auf
// (audit.Log.Record), gegen den Core selbst per Datenbank-Trigger jede
// nachtraegliche Aenderung/Loeschung verweigert (AUD-02).
func (c *Client) Ingest(ctx context.Context, tenantSlug, actor, action, target string, metadata map[string]any) error {
body, err := json.Marshal(ingestRequest{
TenantSlug: tenantSlug, Actor: actor, Action: action, Target: target, Metadata: metadata,
})
if err != nil {
return fmt.Errorf("auditclient: request kodieren: %w", err)
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.BaseURL+"/audit/ingest", bytes.NewReader(body))
if err != nil {
return fmt.Errorf("auditclient: request bauen: %w", err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-Service-Token", c.ServiceToken)
resp, err := c.httpClient().Do(req)
if err != nil {
return fmt.Errorf("auditclient: aufruf fehlgeschlagen: %w", err)
}
defer func() { _ = resp.Body.Close() }()
if resp.StatusCode != http.StatusAccepted {
return fmt.Errorf("auditclient: unerwarteter status %d", resp.StatusCode)
}
return nil
}
func (c *Client) httpClient() *http.Client {
if c.HTTPClient != nil {
return c.HTTPClient
}
return httpclient.Default()
}
@@ -1,48 +0,0 @@
package auditclient
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
)
func TestIngest_SendsExpectedRequest(t *testing.T) {
var gotToken string
var gotBody ingestRequest
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
gotToken = r.Header.Get("X-Service-Token")
_ = json.NewDecoder(r.Body).Decode(&gotBody)
w.WriteHeader(http.StatusAccepted)
}))
defer srv.Close()
c := New(srv.URL, "test-token")
err := c.Ingest(context.Background(), "tenant_a", "svc:ai", "ai.decision.classify", "doc:1",
map[string]any{"model": "llama3", "prompt_version": 2})
if err != nil {
t.Fatalf("Ingest: %v", err)
}
if gotToken != "test-token" {
t.Errorf("token = %q, want test-token", gotToken)
}
if gotBody.Action != "ai.decision.classify" || gotBody.Target != "doc:1" {
t.Errorf("unerwarteter request: %+v", gotBody)
}
if gotBody.Metadata["model"] != "llama3" {
t.Errorf("metadata.model = %v, want llama3", gotBody.Metadata["model"])
}
}
func TestIngest_UnexpectedStatusIsError(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.Error(w, "zugriff verweigert", http.StatusForbidden)
}))
defer srv.Close()
c := New(srv.URL, "test-token")
if err := c.Ingest(context.Background(), "tenant_a", "svc:ai", "ai.decision.classify", "doc:1", nil); err == nil {
t.Fatal("erwartet fehler bei status 403, habe nil")
}
}
+1 -1
View File
@@ -9,11 +9,11 @@ import (
"strings"
"testing"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/auditclient"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/decisionaudit"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/provider"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/provider/mock"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/provider/ollama"
"gitea.perlbach24.de/scripte/nexarch/common/auditclient"
)
var testSchema = Schema{Categories: []string{"rechnung", "vertrag", "kuendigung"}}
+1 -1
View File
@@ -8,7 +8,7 @@ import (
"testing"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/migrate"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/testdb"
"gitea.perlbach24.de/scripte/nexarch/common/testdb"
)
func newStore(t *testing.T) *Store {
@@ -9,7 +9,7 @@ import (
"gitea.perlbach24.de/scripte/nexarch/ai/internal/migrate"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/moduletype"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/testdb"
"gitea.perlbach24.de/scripte/nexarch/common/testdb"
)
// --- Vertrag 1: GOV-01 registrierter Objekttyp (moduletype.ObjectType) ---
+1 -1
View File
@@ -7,7 +7,7 @@ import (
"testing"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/migrate"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/testdb"
"gitea.perlbach24.de/scripte/nexarch/common/testdb"
)
func newRecorder(t *testing.T) *Recorder {
@@ -0,0 +1,90 @@
package correspondentsuggest
import (
"context"
"encoding/json"
"errors"
"fmt"
"strings"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/provider"
)
type Suggestion struct {
Name string `json:"name"`
Confidence float64 `json:"confidence"`
Existing bool `json:"existing"`
}
type Suggester struct {
Provider provider.Provider
Model string
}
func (s *Suggester) Suggest(ctx context.Context, existing []string, moduleContext, documentText string) (Suggestion, bool, error) {
if s.Provider == nil {
return Suggestion{}, false, errors.New("correspondentsuggest: kein provider konfiguriert")
}
resp, err := s.Provider.Chat(ctx, provider.ChatRequest{
Model: s.Model,
Messages: []provider.Message{
{Role: provider.RoleSystem, Content: "Du bist ein Assistent zur Absender-Erkennung. Antworte ausschließlich mit einem JSON-Objekt, ohne Erklärtext."},
{Role: provider.RoleUser, Content: buildPrompt(existing, moduleContext, documentText)},
},
Temperature: 0,
})
if err != nil {
return Suggestion{}, false, fmt.Errorf("correspondentsuggest: modellaufruf: %w", err)
}
sug, ok := parse(existing, resp.Message.Content)
return sug, ok, nil
}
func buildPrompt(existing []string, moduleContext, documentText string) string {
var b strings.Builder
b.WriteString("Nenne den Korrespondenten (Absender bzw. Gegenpartei) des folgenden Dokuments.\n")
if moduleContext != "" {
fmt.Fprintf(&b, "Kontext: das Dokument stammt aus dem Modul %q.\n", moduleContext)
}
if len(existing) > 0 {
b.WriteString("Bekannte Korrespondenten (wähle exakt einen davon, wenn er der Absender ist; ist der Absender keiner davon, nenne ihn so, wie er im Dokument steht): ")
b.WriteString(strings.Join(existing, ", "))
b.WriteString("\n")
}
b.WriteString(`Antworte NUR mit einem JSON-Objekt: {"name": string, "confidence": number zwischen 0 und 1}. Ist kein Korrespondent erkennbar, setze name auf "".` + "\n\nDokument:\n")
b.WriteString(documentText)
return b.String()
}
func parse(existing []string, raw string) (Suggestion, bool) {
start, end := strings.IndexByte(raw, '{'), strings.LastIndexByte(raw, '}')
if start < 0 || end < start {
return Suggestion{}, false
}
var in Suggestion
if err := json.Unmarshal([]byte(raw[start:end+1]), &in); err != nil {
return Suggestion{}, false
}
name := strings.Join(strings.Fields(in.Name), " ")
if name == "" {
return Suggestion{}, false
}
out := Suggestion{Name: name, Confidence: clamp(in.Confidence)}
for _, e := range existing {
if strings.EqualFold(strings.Join(strings.Fields(e), " "), name) {
out.Name, out.Existing = e, true
break
}
}
return out, true
}
func clamp(v float64) float64 {
if v < 0 {
return 0
}
if v > 1 {
return 1
}
return v
}
@@ -0,0 +1,47 @@
package correspondentsuggest
import (
"context"
"os"
"testing"
"time"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/provider/ollama"
)
func TestSuggest_RealInference_Accuracy(t *testing.T) {
base, model := os.Getenv("TEST_OLLAMA_BASE_URL"), os.Getenv("TEST_OLLAMA_MODEL")
if base == "" || model == "" {
t.Skip("TEST_OLLAMA_BASE_URL/TEST_OLLAMA_MODEL nicht gesetzt, Realtest uebersprungen")
}
s := &Suggester{Provider: ollama.New(base), Model: model}
existing := []string{"Musterfirma GmbH", "Beispiel AG", "Stadtwerke Beispielstadt"}
cases := []struct {
name, text, want string
wantExisting bool
}{
{"rechnung", "Musterfirma GmbH\nMusterstrasse 1, 12345 Musterstadt\n\nRechnung Nr. 2024-0451 an Beispiel Consulting\nBetrag 1.297,00 EUR", "Musterfirma GmbH", true},
{"vertrag", "Kuendigung des Dienstleistungsvertrags\nBeispiel AG, Vorstand\nBeispielweg 5, 54321 Beispieldorf\n\nSehr geehrte Damen und Herren, hiermit kuendigen wir den Vertrag zum Quartalsende.", "Beispiel AG", true},
{"neu", "Neuer Absender KG\nTeststrasse 9\n\nAngebot Nr. 17 fuer Buerobedarf, gueltig bis 31.12.2026.", "Neuer Absender KG", false},
{"strom", "Stadtwerke Beispielstadt\nJahresabrechnung Strom 2026\nVerbrauch 2.431 kWh, Nachzahlung 84,12 EUR", "Stadtwerke Beispielstadt", true},
}
hits := 0
for _, c := range cases {
ctx, cancel := context.WithTimeout(context.Background(), 90*time.Second)
got, ok, err := s.Suggest(ctx, existing, "dms", c.text)
cancel()
if err != nil {
t.Fatalf("%s: %v", c.name, err)
}
t.Logf("%s: ok=%v %+v", c.name, ok, got)
if ok && got.Name == c.want && got.Existing == c.wantExisting && got.Confidence >= 0 && got.Confidence <= 1 {
hits++
}
}
t.Logf("Trefferquote: %d/%d", hits, len(cases))
if hits < 3 {
t.Errorf("trefferquote %d/%d unter 3/4", hits, len(cases))
}
}
@@ -0,0 +1,52 @@
package correspondentsuggest
import (
"context"
"errors"
"testing"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/provider"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/provider/mock"
)
func newSuggester(resp string) *Suggester {
return &Suggester{
Provider: &mock.Provider{ChatResponse: provider.ChatResponse{Message: provider.Message{Role: provider.RoleAssistant, Content: resp}}},
}
}
func TestSuggest_NormalizesToExisting(t *testing.T) {
got, ok, err := newSuggester(`{"name":" muster gmbh ","confidence":0.8}`).
Suggest(context.Background(), []string{"Beispiel AG", "Muster GmbH"}, "dms", "Brief")
if err != nil || !ok {
t.Fatalf("ok=%v err=%v", ok, err)
}
if got.Name != "Muster GmbH" || !got.Existing || got.Confidence != 0.8 {
t.Fatalf("unerwartet: %+v", got)
}
}
func TestSuggest_NewCorrespondentNotMarkedExisting(t *testing.T) {
got, ok, _ := newSuggester(`{"name":"Neu KG","confidence":7}`).Suggest(context.Background(), []string{"Alt AG"}, "", "x")
if !ok || got.Existing || got.Confidence != 1 {
t.Fatalf("unerwartet: %+v ok=%v", got, ok)
}
}
func TestSuggest_NoneOrGarbage(t *testing.T) {
for _, raw := range []string{`{"name":"","confidence":0.9}`, "nix", `{"name":`} {
if _, ok, err := newSuggester(raw).Suggest(context.Background(), nil, "", "x"); ok || err != nil {
t.Fatalf("raw=%q ok=%v err=%v", raw, ok, err)
}
}
}
func TestSuggest_Errors(t *testing.T) {
s := &Suggester{Provider: &mock.Provider{Err: errors.New("down")}}
if _, _, err := s.Suggest(context.Background(), nil, "", "x"); err == nil {
t.Fatal("fehler erwartet")
}
if _, _, err := (&Suggester{}).Suggest(context.Background(), nil, "", "x"); err == nil {
t.Fatal("fehler erwartet")
}
}
+1 -1
View File
@@ -8,7 +8,7 @@ import (
"context"
"fmt"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/auditclient"
"gitea.perlbach24.de/scripte/nexarch/common/auditclient"
)
// Decision ist eine einzelne, abgeschlossene KI-gestuetzte Entscheidung
@@ -7,7 +7,7 @@ import (
"net/http/httptest"
"testing"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/auditclient"
"gitea.perlbach24.de/scripte/nexarch/common/auditclient"
)
type recordedRequest struct {
@@ -7,10 +7,10 @@ import (
"testing"
"time"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/loadtest"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/provider"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/provider/ollama"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/semanticsearch"
"gitea.perlbach24.de/scripte/nexarch/common/loadtest"
)
// QA-08 Pruefung 1 (AK1): echte parallele Chat-Anfragen gegen den realen,
+1 -1
View File
@@ -12,8 +12,8 @@ import (
"gitea.perlbach24.de/scripte/nexarch/ai/internal/docchat"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/semanticsearch"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/svcauth"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/vectorindex"
"gitea.perlbach24.de/scripte/nexarch/common/svcauth"
)
type Server struct {
+210
View File
@@ -0,0 +1,210 @@
package doclang
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"os"
"regexp"
"strings"
"unicode"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgxpool"
)
const EnvLanguagesFile = "NEXARCH_AI_LANGUAGES_FILE"
const (
minTokens = 5
minBestHits = 2
codePatternRe = `^[a-z]{2,3}$`
)
var (
ErrUnknownLanguage = errors.New("doclang: sprache nicht registriert")
ErrInvalidCode = errors.New("doclang: sprachcode muss 2 bis 3 kleinbuchstaben haben")
codePattern = regexp.MustCompile(codePatternRe)
)
type Language struct {
Code string `json:"code"`
Name string `json:"name"`
Stopwords []string `json:"stopwords"`
}
type Registry struct {
langs map[string]Language
words map[string]map[string]bool
}
func DefaultRegistry() *Registry {
r := &Registry{langs: map[string]Language{}, words: map[string]map[string]bool{}}
_ = r.Register(Language{Code: "de", Name: "Deutsch", Stopwords: strings.Fields(
"der die das und ist nicht ein eine mit von zu den für auf dem des auch sich wir sie ich bei nach wird sind oder aus werden wurde über sehr nur noch")})
_ = r.Register(Language{Code: "en", Name: "English", Stopwords: strings.Fields(
"the and is of to that for with on are this by or from at it not have was were will would which their been you your has had")})
return r
}
func (r *Registry) Register(l Language) error {
l.Code = strings.ToLower(strings.TrimSpace(l.Code))
if !codePattern.MatchString(l.Code) {
return ErrInvalidCode
}
if strings.TrimSpace(l.Name) == "" {
l.Name = l.Code
}
set := make(map[string]bool, len(l.Stopwords))
for _, w := range l.Stopwords {
if w = strings.ToLower(strings.TrimSpace(w)); w != "" {
set[w] = true
}
}
r.langs[l.Code] = l
r.words[l.Code] = set
return nil
}
func (r *Registry) Lookup(code string) (Language, bool) {
l, ok := r.langs[strings.ToLower(strings.TrimSpace(code))]
return l, ok
}
func (r *Registry) LoadJSON(src io.Reader) error {
var langs []Language
if err := json.NewDecoder(src).Decode(&langs); err != nil {
return fmt.Errorf("doclang: sprachdatei lesen: %w", err)
}
for _, l := range langs {
if err := r.Register(l); err != nil {
return fmt.Errorf("doclang: sprache %q: %w", l.Code, err)
}
}
return nil
}
func RegistryFromEnv() (*Registry, error) {
return LoadRegistryFile(os.Getenv(EnvLanguagesFile))
}
// LoadRegistryFile ergaenzt die Standardsprachen um die Sprachen aus path; leerer Pfad = nur Standard.
func LoadRegistryFile(path string) (*Registry, error) {
r := DefaultRegistry()
if path == "" {
return r, nil
}
f, err := os.Open(path)
if err != nil {
return nil, fmt.Errorf("doclang: %s: %w", EnvLanguagesFile, err)
}
defer func() { _ = f.Close() }()
if err := r.LoadJSON(f); err != nil {
return nil, err
}
return r, nil
}
// Detect liefert die wahrscheinlichste registrierte Sprache oder "" bei unklarem Text.
func (r *Registry) Detect(text string) (Language, bool) {
tokens := strings.FieldsFunc(strings.ToLower(text), func(c rune) bool { return !unicode.IsLetter(c) })
if len(tokens) < minTokens {
return Language{}, false
}
best, second, bestCode := 0, 0, ""
for code, words := range r.words {
hits := 0
for _, t := range tokens {
if words[t] {
hits++
}
}
switch {
case hits > best || (hits == best && code < bestCode):
second, best, bestCode = best, hits, code
case hits > second:
second = hits
}
}
if best < minBestHits || best == second {
return Language{}, false
}
return r.langs[bestCode], true
}
type TenantLanguages interface {
Get(ctx context.Context, tenantSlug string) (string, error)
}
type Resolved struct {
Document Language
Output Language
}
type Resolver struct {
Registry *Registry
Tenants TenantLanguages
}
// Resolve ermittelt Dokument- und Ausgabesprache. Dokumentsprache: explizit, sonst erkannt.
// Ausgabesprache: Mandantenkonfiguration, sonst Dokumentsprache. Leer, wenn beides unbekannt.
func (r *Resolver) Resolve(ctx context.Context, tenantSlug, explicitDocLang, text string) (Resolved, error) {
var out Resolved
if explicitDocLang != "" {
l, ok := r.Registry.Lookup(explicitDocLang)
if !ok {
return Resolved{}, fmt.Errorf("%w: %q", ErrUnknownLanguage, explicitDocLang)
}
out.Document = l
} else if l, ok := r.Registry.Detect(text); ok {
out.Document = l
}
out.Output = out.Document
code, err := r.Tenants.Get(ctx, tenantSlug)
if err != nil {
return Resolved{}, err
}
if l, ok := r.Registry.Lookup(code); ok {
out.Output = l
}
return out, nil
}
type Store struct {
Pool *pgxpool.Pool
}
func (s *Store) Set(ctx context.Context, tenantSlug, code string) error {
tenantSlug = strings.TrimSpace(tenantSlug)
code = strings.ToLower(strings.TrimSpace(code))
if tenantSlug == "" {
return fmt.Errorf("doclang: tenantSlug darf nicht leer sein")
}
if !codePattern.MatchString(code) {
return ErrInvalidCode
}
_, err := s.Pool.Exec(ctx, `
INSERT INTO ai_tenant_languages (tenant_slug, language, updated_at)
VALUES ($1, $2, now())
ON CONFLICT (tenant_slug) DO UPDATE SET language = EXCLUDED.language, updated_at = now()
`, tenantSlug, code)
if err != nil {
return fmt.Errorf("doclang: speichern: %w", err)
}
return nil
}
// Get liefert "" ohne Konfiguration; liest live, Aenderungen wirken ohne Neustart.
func (s *Store) Get(ctx context.Context, tenantSlug string) (string, error) {
var code string
err := s.Pool.QueryRow(ctx, `SELECT language FROM ai_tenant_languages WHERE tenant_slug = $1`, tenantSlug).Scan(&code)
if errors.Is(err, pgx.ErrNoRows) {
return "", nil
}
if err != nil {
return "", fmt.Errorf("doclang: lesen: %w", err)
}
return code, nil
}
+202
View File
@@ -0,0 +1,202 @@
package doclang
import (
"context"
"errors"
"os"
"path/filepath"
"strings"
"testing"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/migrate"
"gitea.perlbach24.de/scripte/nexarch/common/testdb"
)
const (
deText = "Sehr geehrte Damen und Herren, die Rechnung ist nicht bezahlt und wird von uns mit der Mahnung an Sie gesendet."
enText = "Dear Sirs, the invoice is not paid and will be sent to you by the reminder that we have prepared for your account."
frJSON = `[{"code":"fr","name":"Français","stopwords":["le","la","les","et","est","des","une","pour","dans","avec"]}]`
)
type fakeTenants map[string]string
func (f fakeTenants) Get(_ context.Context, tenant string) (string, error) { return f[tenant], nil }
func TestDetect_GermanAndEnglish(t *testing.T) {
r := DefaultRegistry()
if l, ok := r.Detect(deText); !ok || l.Code != "de" {
t.Fatalf("de: %+v %v", l, ok)
}
if l, ok := r.Detect(enText); !ok || l.Code != "en" {
t.Fatalf("en: %+v %v", l, ok)
}
}
func TestDetect_UnclearYieldsNothing(t *testing.T) {
r := DefaultRegistry()
for _, txt := range []string{"", "Rechnung 2026", "xyz abc qwe rty uio pas dfg hjk"} {
if l, ok := r.Detect(txt); ok {
t.Errorf("%q: unerwartet %+v", txt, l)
}
}
}
func TestRegistry_ExtensibleByJSONWithoutCodeChange(t *testing.T) {
r := DefaultRegistry()
if err := r.LoadJSON(strings.NewReader(frJSON)); err != nil {
t.Fatal(err)
}
l, ok := r.Detect("Le contrat est signé et les parties des deux côtés sont dans une situation pour avec")
if !ok || l.Code != "fr" || l.Name != "Français" {
t.Fatalf("fr: %+v %v", l, ok)
}
if l, ok := r.Detect(deText); !ok || l.Code != "de" {
t.Fatalf("de nach erweiterung: %+v %v", l, ok)
}
}
func TestRegistryFromEnv(t *testing.T) {
path := filepath.Join(t.TempDir(), "langs.json")
if err := os.WriteFile(path, []byte(frJSON), 0o600); err != nil {
t.Fatal(err)
}
t.Setenv(EnvLanguagesFile, path)
r, err := RegistryFromEnv()
if err != nil {
t.Fatal(err)
}
if _, ok := r.Lookup("fr"); !ok {
t.Fatal("fr fehlt")
}
t.Setenv(EnvLanguagesFile, filepath.Join(t.TempDir(), "fehlt.json"))
if _, err := RegistryFromEnv(); err == nil {
t.Fatal("fehler erwartet")
}
}
func TestLoadJSON_RejectsBadCode(t *testing.T) {
r := DefaultRegistry()
if err := r.LoadJSON(strings.NewReader(`[{"code":"Deutsch","stopwords":[]}]`)); !errors.Is(err, ErrInvalidCode) {
t.Fatalf("err=%v", err)
}
}
func TestResolve_OutputFollowsTenantOtherwiseDocument(t *testing.T) {
r := &Resolver{Registry: DefaultRegistry(), Tenants: fakeTenants{"acme": "de"}}
got, err := r.Resolve(context.Background(), "acme", "", enText)
if err != nil || got.Document.Code != "en" || got.Output.Code != "de" {
t.Fatalf("%+v %v", got, err)
}
got, _ = r.Resolve(context.Background(), "other", "", enText)
if got.Output.Code != "en" {
t.Fatalf("ohne konfiguration: %+v", got)
}
}
func TestResolve_ExplicitOverridesDetectionAndUnknownFails(t *testing.T) {
r := &Resolver{Registry: DefaultRegistry(), Tenants: fakeTenants{}}
got, err := r.Resolve(context.Background(), "acme", "de", enText)
if err != nil || got.Document.Code != "de" {
t.Fatalf("%+v %v", got, err)
}
if _, err := r.Resolve(context.Background(), "acme", "xx", enText); !errors.Is(err, ErrUnknownLanguage) {
t.Fatalf("err=%v", err)
}
}
func TestResolve_UnknownTenantCodeFallsBackToDocument(t *testing.T) {
r := &Resolver{Registry: DefaultRegistry(), Tenants: fakeTenants{"acme": "fr"}}
got, _ := r.Resolve(context.Background(), "acme", "", enText)
if got.Output.Code != "en" {
t.Fatalf("%+v", got)
}
}
func newStore(t *testing.T) *Store {
t.Helper()
dsn := os.Getenv("TEST_TENANT_DSN")
if dsn == "" {
t.Skip("TEST_TENANT_DSN nicht gesetzt, Integrationstest übersprungen")
}
pool := testdb.Pool(t, dsn)
dir, err := filepath.Abs("../../migrations")
if err != nil {
t.Fatal(err)
}
migrations, err := migrate.Load(dir)
if err != nil {
t.Fatal(err)
}
if _, err := migrate.Up(context.Background(), pool, migrations); err != nil {
t.Fatal(err)
}
return &Store{Pool: pool}
}
func TestStore_PerTenantAndLive(t *testing.T) {
s := newStore(t)
ctx := context.Background()
if got, err := s.Get(ctx, "acme"); err != nil || got != "" {
t.Fatalf("%q %v", got, err)
}
if err := s.Set(ctx, "acme", " DE "); err != nil {
t.Fatal(err)
}
if err := s.Set(ctx, "other", "en"); err != nil {
t.Fatal(err)
}
if got, _ := s.Get(ctx, "acme"); got != "de" {
t.Fatalf("acme: %q", got)
}
if err := s.Set(ctx, "acme", "en"); err != nil {
t.Fatal(err)
}
if got, _ := s.Get(ctx, "acme"); got != "en" {
t.Fatalf("wechsel ohne neustart: %q", got)
}
if got, _ := s.Get(ctx, "other"); got != "en" {
t.Fatalf("other: %q", got)
}
if err := s.Set(ctx, "acme", "Deutsch"); !errors.Is(err, ErrInvalidCode) {
t.Fatalf("err=%v", err)
}
}
var testset = []struct{ code, text string }{
{"de", "Hiermit kündigen wir den Vertrag zum Ende des Quartals und bitten um eine schriftliche Bestätigung."},
{"de", "Die Rechnung wurde am 15. November versendet und ist innerhalb von 14 Tagen zu bezahlen."},
{"de", "Wir bedanken uns für Ihre Bestellung und senden Ihnen die Ware in den nächsten Tagen zu."},
{"de", "Der Mieter ist verpflichtet, die Wohnung bei Auszug in einem sauberen Zustand zu übergeben."},
{"de", "Sehr geehrte Frau Muster, anbei erhalten Sie die Unterlagen, die Sie angefordert haben."},
{"de", "Das Angebot gilt nur für Kunden, die sich bis zum Jahresende für den Vertrag entscheiden."},
{"de", "Aus technischen Gründen wird die Lieferung erst nach dem Feiertag bei Ihnen eintreffen."},
{"de", "Ich bitte Sie, mir den Termin für die Besichtigung der Räume noch heute mitzuteilen."},
{"de", "Die Gesellschafter haben in der Versammlung beschlossen, den Gewinn auf neue Rechnung vorzutragen."},
{"de", "Eine Kopie des Bescheids ist diesem Schreiben beigefügt und wird auch an Ihren Steuerberater gesendet."},
{"en", "We hereby terminate the contract at the end of the quarter and ask for a written confirmation."},
{"en", "The invoice has been sent on the fifteenth of November and is payable within fourteen days."},
{"en", "Thank you for your order, the goods will be shipped to you within the next few days."},
{"en", "The tenant is obliged to hand over the apartment in a clean condition when moving out."},
{"en", "Dear Ms Sample, please find attached the documents that you have requested from us."},
{"en", "This offer is only valid for customers who decide on the contract before the end of the year."},
{"en", "Due to technical reasons the delivery will not arrive before the public holiday."},
{"en", "I would like to ask you to let me know the date for the inspection of the premises today."},
{"en", "The shareholders have resolved at the meeting that the profit will be carried forward to the next year."},
{"en", "A copy of the decision is attached to this letter and will also be sent to your tax advisor."},
}
func TestDetect_SyntheticTestsetAccuracy(t *testing.T) {
r := DefaultRegistry()
hits := 0
for _, c := range testset {
if l, ok := r.Detect(c.text); ok && l.Code == c.code {
hits++
} else {
t.Logf("fehlklassifiziert/unklar (%s): %q -> %+v ok=%v", c.code, c.text, l, ok)
}
}
t.Logf("Erkennungsquote: %d/%d", hits, len(testset))
if hits < len(testset)*9/10 {
t.Errorf("erkennungsquote %d/%d unter 90%%", hits, len(testset))
}
}
+1 -1
View File
@@ -11,7 +11,7 @@ import (
"gitea.perlbach24.de/scripte/nexarch/ai/internal/classify"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/migrate"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/provider"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/testdb"
"gitea.perlbach24.de/scripte/nexarch/common/testdb"
"github.com/jackc/pgx/v5/pgxpool"
)
+1 -1
View File
@@ -14,7 +14,7 @@ import (
"gitea.perlbach24.de/scripte/nexarch/ai/internal/migrate"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/provider"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/queue"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/testdb"
"gitea.perlbach24.de/scripte/nexarch/common/testdb"
)
func newPool(t *testing.T) *pgxpool.Pool {
+1 -1
View File
@@ -15,7 +15,7 @@ import (
"gitea.perlbach24.de/scripte/nexarch/ai/internal/extractvalidate"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/invoiceextract"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/svcauth"
"gitea.perlbach24.de/scripte/nexarch/common/svcauth"
)
type Server struct {
@@ -15,7 +15,7 @@ import (
"gitea.perlbach24.de/scripte/nexarch/ai/internal/extractmapping"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/metadatapushclient"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/svcauth"
"gitea.perlbach24.de/scripte/nexarch/common/svcauth"
)
type Server struct {
@@ -13,7 +13,7 @@ import (
"gitea.perlbach24.de/scripte/nexarch/ai/internal/extractmapping"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/metadatapushclient"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/migrate"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/testdb"
"gitea.perlbach24.de/scripte/nexarch/common/testdb"
)
func newMappingRegistry(t *testing.T) *extractmapping.Registry {
@@ -10,7 +10,7 @@ import (
"testing"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/migrate"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/testdb"
"gitea.perlbach24.de/scripte/nexarch/common/testdb"
)
func newRegistry(t *testing.T) *Registry {
+641
View File
@@ -0,0 +1,641 @@
package featureapi
import (
"context"
"encoding/json"
"errors"
"net/http"
"strings"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/classify"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/confidence"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/correction"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/decisionaudit"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/doctype"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/embedpipeline"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/highlight"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/modelconfig"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/prompttemplate"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/summaryquality"
"gitea.perlbach24.de/scripte/nexarch/common/svcauth"
)
const maxBodyBytes = 4 << 20
// Namen der abschaltbaren Funktionen (tenantfeature) und der Schwellen (confidence).
const (
FunctionClassify = "classify"
FunctionDoctype = "doctype"
FunctionHighlight = "highlight"
)
type Features interface {
Set(ctx context.Context, tenantSlug, function string, enabled bool) error
IsEnabled(ctx context.Context, tenantSlug, function string, globalEnabled bool) (bool, error)
}
type Thresholds interface {
Set(ctx context.Context, tenantSlug, function string, threshold float64) error
Get(ctx context.Context, tenantSlug, function string) (float64, error)
MeetsThreshold(ctx context.Context, tenantSlug, function string, value float64) (bool, error)
}
type Taxonomies interface {
List(ctx context.Context, tenantSlug string) ([]string, error)
Add(ctx context.Context, tenantSlug, code, label string) error
SeedDefaults(ctx context.Context, tenantSlug string) error
}
type Corrections interface {
Record(ctx context.Context, c correction.Correction) error
Aggregate(ctx context.Context, tenantSlug, function string) (correction.Stats, error)
}
type Embeddings interface {
Ingest(ctx context.Context, tenantSlug, sourceType, sourceID, text string) ([]embedpipeline.Chunk, error)
Status(ctx context.Context, tenantSlug, sourceType, sourceID string) ([]embedpipeline.ChunkStatus, error)
}
type Models interface {
Set(ctx context.Context, tenantSlug, function, providerName, model string) error
Get(ctx context.Context, tenantSlug, function string) (modelconfig.Selection, error)
}
type Templates interface {
Save(ctx context.Context, functionKey, body, createdBy string) (prompttemplate.Version, error)
Activate(ctx context.Context, functionKey string, version int, activatedBy string) error
Active(ctx context.Context, functionKey string) (prompttemplate.Version, error)
History(ctx context.Context, functionKey string) ([]prompttemplate.Version, error)
}
type Server struct {
Classifier *classify.Classifier
Doctype *doctype.Detector
Highlight *highlight.Detector
Taxonomy Taxonomies
Features Features
Thresholds Thresholds
Corrections Corrections
Embeddings Embeddings
Models Models
Templates Templates
// Audit ist nil ohne Core-Anbindung; dann laufen Klassifizierungen unprotokolliert.
Audit *decisionaudit.Recorder
ServiceToken string
}
func (s *Server) RegisterRoutes(mux *http.ServeMux) {
mux.HandleFunc("POST /classify", s.auth(s.handleClassify))
mux.HandleFunc("POST /doctype/detect", s.auth(s.handleDoctypeDetect))
mux.HandleFunc("GET /tenants/{tenant}/doctypes", s.auth(s.handleDoctypeList))
mux.HandleFunc("POST /tenants/{tenant}/doctypes", s.auth(s.handleDoctypeAdd))
mux.HandleFunc("POST /tenants/{tenant}/doctypes/defaults", s.auth(s.handleDoctypeSeed))
mux.HandleFunc("POST /highlight", s.auth(s.handleHighlight))
mux.HandleFunc("GET /tenants/{tenant}/features/{function}", s.auth(s.handleFeatureGet))
mux.HandleFunc("PUT /tenants/{tenant}/features/{function}", s.auth(s.handleFeaturePut))
mux.HandleFunc("GET /tenants/{tenant}/confidence/{function}", s.auth(s.handleConfidenceGet))
mux.HandleFunc("PUT /tenants/{tenant}/confidence/{function}", s.auth(s.handleConfidencePut))
mux.HandleFunc("GET /tenants/{tenant}/model-config/{function}", s.auth(s.handleModelGet))
mux.HandleFunc("PUT /tenants/{tenant}/model-config/{function}", s.auth(s.handleModelPut))
mux.HandleFunc("POST /corrections", s.auth(s.handleCorrectionRecord))
mux.HandleFunc("GET /tenants/{tenant}/corrections/{function}/stats", s.auth(s.handleCorrectionStats))
mux.HandleFunc("POST /embeddings/ingest", s.auth(s.handleEmbedIngest))
mux.HandleFunc("GET /embeddings/status", s.auth(s.handleEmbedStatus))
mux.HandleFunc("POST /prompt-templates/{key}", s.auth(s.handleTemplateSave))
mux.HandleFunc("GET /prompt-templates/{key}", s.auth(s.handleTemplateActive))
mux.HandleFunc("GET /prompt-templates/{key}/history", s.auth(s.handleTemplateHistory))
mux.HandleFunc("PUT /prompt-templates/{key}/active", s.auth(s.handleTemplateActivate))
mux.HandleFunc("POST /summary-quality/evaluate", s.auth(s.handleSummaryQuality))
}
func (s *Server) auth(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if !svcauth.Equal(r.Header.Get("X-Service-Token"), s.ServiceToken) {
writeError(w, http.StatusUnauthorized, "service-token fehlt oder ist ungueltig")
return
}
next(w, r)
}
}
func decode(w http.ResponseWriter, r *http.Request, v any) bool {
if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, maxBodyBytes)).Decode(v); err != nil {
writeError(w, http.StatusBadRequest, "ungueltiger anfrage-koerper")
return false
}
return true
}
// enabled prueft den Mandantenschalter; der globale Schalter (Core LIC-02) ist hier nicht angebunden und gilt als an.
func (s *Server) enabled(w http.ResponseWriter, r *http.Request, tenant, function string) bool {
ok, err := s.Features.IsEnabled(r.Context(), tenant, function, true)
if err != nil {
writeError(w, http.StatusInternalServerError, "funktionsschalter lesen fehlgeschlagen")
return false
}
if !ok {
writeError(w, http.StatusForbidden, "funktion fuer diesen mandanten deaktiviert")
return false
}
return true
}
func (s *Server) meets(ctx context.Context, tenant, function string, value float64) (bool, error) {
return s.Thresholds.MeetsThreshold(ctx, tenant, function, value)
}
type classifyRequest struct {
TenantSlug string `json:"tenant_slug"`
Categories []string `json:"categories"`
Text string `json:"text"`
Actor string `json:"actor"`
InputRef string `json:"input_ref"`
PromptVersion int `json:"prompt_version"`
}
type classifyResponse struct {
classify.Result
MeetsThreshold bool `json:"meets_threshold"`
Audited bool `json:"audited"`
}
func (s *Server) handleClassify(w http.ResponseWriter, r *http.Request) {
var req classifyRequest
if !decode(w, r, &req) {
return
}
if strings.TrimSpace(req.TenantSlug) == "" || strings.TrimSpace(req.Text) == "" || len(req.Categories) == 0 {
writeError(w, http.StatusBadRequest, "tenant_slug, categories und text sind erforderlich")
return
}
if !s.enabled(w, r, req.TenantSlug, FunctionClassify) {
return
}
schema := classify.Schema{Categories: req.Categories}
audited := s.Audit != nil && strings.TrimSpace(req.Actor) != "" && strings.TrimSpace(req.InputRef) != ""
var (
res classify.Result
err error
)
if audited {
dc := classify.DecisionContext{Recorder: s.Audit, TenantSlug: req.TenantSlug, Actor: req.Actor, PromptVersion: req.PromptVersion}
res, err = s.Classifier.ClassifyWithSchemaAudited(r.Context(), schema, req.Text, dc, req.InputRef)
} else {
res, err = s.Classifier.ClassifyWithSchema(r.Context(), schema, req.Text)
}
if err != nil {
writeError(w, http.StatusBadGateway, "klassifizierung fehlgeschlagen")
return
}
ok, err := s.meets(r.Context(), req.TenantSlug, FunctionClassify, res.Confidence)
if err != nil {
writeError(w, http.StatusInternalServerError, "schwellenwert lesen fehlgeschlagen")
return
}
writeJSON(w, http.StatusOK, classifyResponse{Result: res, MeetsThreshold: ok, Audited: audited})
}
type doctypeResponse struct {
DocType string `json:"doc_type"`
Confidence float64 `json:"confidence"`
Ambiguous bool `json:"ambiguous"`
Reasoning string `json:"reasoning,omitempty"`
MeetsThreshold bool `json:"meets_threshold"`
}
func (s *Server) handleDoctypeDetect(w http.ResponseWriter, r *http.Request) {
var req struct {
TenantSlug string `json:"tenant_slug"`
Text string `json:"text"`
}
if !decode(w, r, &req) {
return
}
if strings.TrimSpace(req.TenantSlug) == "" || strings.TrimSpace(req.Text) == "" {
writeError(w, http.StatusBadRequest, "tenant_slug und text sind erforderlich")
return
}
if !s.enabled(w, r, req.TenantSlug, FunctionDoctype) {
return
}
cats, err := s.Taxonomy.List(r.Context(), req.TenantSlug)
if err != nil {
writeError(w, http.StatusInternalServerError, "taxonomie lesen fehlgeschlagen")
return
}
if len(cats) == 0 {
writeError(w, http.StatusConflict, "keine dokumenttyp-taxonomie fuer diesen mandanten")
return
}
res, err := s.Doctype.Detect(r.Context(), req.TenantSlug, req.Text)
if err != nil {
writeError(w, http.StatusBadGateway, "dokumenttyp-erkennung fehlgeschlagen")
return
}
ok, err := s.meets(r.Context(), req.TenantSlug, FunctionDoctype, res.Confidence)
if err != nil {
writeError(w, http.StatusInternalServerError, "schwellenwert lesen fehlgeschlagen")
return
}
writeJSON(w, http.StatusOK, doctypeResponse{DocType: res.DocType, Confidence: res.Confidence, Ambiguous: res.Ambiguous, Reasoning: res.Reasoning, MeetsThreshold: ok})
}
func (s *Server) handleDoctypeList(w http.ResponseWriter, r *http.Request) {
codes, err := s.Taxonomy.List(r.Context(), r.PathValue("tenant"))
if err != nil {
writeError(w, http.StatusInternalServerError, "taxonomie lesen fehlgeschlagen")
return
}
if codes == nil {
codes = []string{}
}
writeJSON(w, http.StatusOK, map[string]any{"doc_types": codes})
}
func (s *Server) handleDoctypeAdd(w http.ResponseWriter, r *http.Request) {
var req struct {
Code string `json:"code"`
Label string `json:"label"`
}
if !decode(w, r, &req) {
return
}
if strings.TrimSpace(req.Code) == "" || strings.TrimSpace(req.Label) == "" {
writeError(w, http.StatusBadRequest, "code und label sind erforderlich")
return
}
if err := s.Taxonomy.Add(r.Context(), r.PathValue("tenant"), req.Code, req.Label); err != nil {
writeError(w, http.StatusInternalServerError, "dokumenttyp speichern fehlgeschlagen")
return
}
s.handleDoctypeList(w, r)
}
func (s *Server) handleDoctypeSeed(w http.ResponseWriter, r *http.Request) {
if err := s.Taxonomy.SeedDefaults(r.Context(), r.PathValue("tenant")); err != nil {
writeError(w, http.StatusInternalServerError, "standard-dokumenttypen anlegen fehlgeschlagen")
return
}
s.handleDoctypeList(w, r)
}
func (s *Server) handleHighlight(w http.ResponseWriter, r *http.Request) {
var req struct {
TenantSlug string `json:"tenant_slug"`
Text string `json:"text"`
}
if !decode(w, r, &req) {
return
}
if strings.TrimSpace(req.TenantSlug) == "" || strings.TrimSpace(req.Text) == "" {
writeError(w, http.StatusBadRequest, "tenant_slug und text sind erforderlich")
return
}
if !s.enabled(w, r, req.TenantSlug, FunctionHighlight) {
return
}
passages, err := s.Highlight.DetectImportant(r.Context(), req.Text)
resp := map[string]any{"passages": nonNil(passages)}
if err != nil {
// Teilergebnisse bleiben erhalten (highlight liefert sie neben dem Fehler).
if len(passages) == 0 {
writeError(w, http.StatusBadGateway, "textstellen-erkennung fehlgeschlagen")
return
}
resp["partial"] = true
}
writeJSON(w, http.StatusOK, resp)
}
func nonNil(p []highlight.Passage) []highlight.Passage {
if p == nil {
return []highlight.Passage{}
}
return p
}
func (s *Server) handleFeatureGet(w http.ResponseWriter, r *http.Request) {
on, err := s.Features.IsEnabled(r.Context(), r.PathValue("tenant"), r.PathValue("function"), true)
if err != nil {
writeError(w, http.StatusInternalServerError, "funktionsschalter lesen fehlgeschlagen")
return
}
writeJSON(w, http.StatusOK, map[string]bool{"enabled": on})
}
func (s *Server) handleFeaturePut(w http.ResponseWriter, r *http.Request) {
var req struct {
Enabled *bool `json:"enabled"`
}
if !decode(w, r, &req) {
return
}
if req.Enabled == nil {
writeError(w, http.StatusBadRequest, "enabled ist erforderlich")
return
}
if err := s.Features.Set(r.Context(), r.PathValue("tenant"), r.PathValue("function"), *req.Enabled); err != nil {
writeError(w, http.StatusBadRequest, "funktionsschalter speichern fehlgeschlagen")
return
}
s.handleFeatureGet(w, r)
}
func (s *Server) handleConfidenceGet(w http.ResponseWriter, r *http.Request) {
v, err := s.Thresholds.Get(r.Context(), r.PathValue("tenant"), r.PathValue("function"))
if err != nil {
writeError(w, http.StatusInternalServerError, "schwellenwert lesen fehlgeschlagen")
return
}
writeJSON(w, http.StatusOK, map[string]float64{"threshold": v})
}
func (s *Server) handleConfidencePut(w http.ResponseWriter, r *http.Request) {
var req struct {
Threshold *float64 `json:"threshold"`
}
if !decode(w, r, &req) {
return
}
if req.Threshold == nil {
writeError(w, http.StatusBadRequest, "threshold ist erforderlich")
return
}
if err := s.Thresholds.Set(r.Context(), r.PathValue("tenant"), r.PathValue("function"), *req.Threshold); err != nil {
if errors.Is(err, confidence.ErrInvalidThreshold) {
writeError(w, http.StatusBadRequest, err.Error())
return
}
writeError(w, http.StatusInternalServerError, "schwellenwert speichern fehlgeschlagen")
return
}
s.handleConfidenceGet(w, r)
}
func (s *Server) handleModelGet(w http.ResponseWriter, r *http.Request) {
sel, err := s.Models.Get(r.Context(), r.PathValue("tenant"), r.PathValue("function"))
if errors.Is(err, modelconfig.ErrNotFound) {
writeError(w, http.StatusNotFound, "keine modellwahl gesetzt")
return
}
if err != nil {
writeError(w, http.StatusInternalServerError, "modellwahl lesen fehlgeschlagen")
return
}
writeJSON(w, http.StatusOK, map[string]string{"provider": sel.ProviderName, "model": sel.Model})
}
func (s *Server) handleModelPut(w http.ResponseWriter, r *http.Request) {
var req struct {
Provider string `json:"provider"`
Model string `json:"model"`
}
if !decode(w, r, &req) {
return
}
if strings.TrimSpace(req.Provider) == "" || strings.TrimSpace(req.Model) == "" {
writeError(w, http.StatusBadRequest, "provider und model sind erforderlich")
return
}
err := s.Models.Set(r.Context(), r.PathValue("tenant"), r.PathValue("function"), req.Provider, req.Model)
switch {
case errors.Is(err, modelconfig.ErrUnknownProvider), errors.Is(err, modelconfig.ErrModelNotAvailable):
writeError(w, http.StatusBadRequest, err.Error())
return
case err != nil:
writeError(w, http.StatusBadGateway, "modellwahl speichern fehlgeschlagen")
return
}
s.handleModelGet(w, r)
}
func (s *Server) handleCorrectionRecord(w http.ResponseWriter, r *http.Request) {
var req struct {
TenantSlug string `json:"tenant_slug"`
Function string `json:"function"`
InputRef string `json:"input_ref"`
SuggestedCategory string `json:"suggested_category"`
SuggestedConfidence float64 `json:"suggested_confidence"`
CorrectedCategory string `json:"corrected_category"`
Actor string `json:"actor"`
}
if !decode(w, r, &req) {
return
}
err := s.Corrections.Record(r.Context(), correction.Correction{
TenantSlug: req.TenantSlug, Function: req.Function, InputRef: req.InputRef,
SuggestedCategory: req.SuggestedCategory, SuggestedConfidence: req.SuggestedConfidence,
CorrectedCategory: req.CorrectedCategory, Actor: req.Actor,
})
if err != nil {
// Record validiert vor dem Schreiben; ein Fehler ist hier fast immer eine unvollstaendige Anfrage.
writeError(w, http.StatusBadRequest, err.Error())
return
}
w.WriteHeader(http.StatusNoContent)
}
func (s *Server) handleCorrectionStats(w http.ResponseWriter, r *http.Request) {
st, err := s.Corrections.Aggregate(r.Context(), r.PathValue("tenant"), r.PathValue("function"))
if err != nil {
writeError(w, http.StatusInternalServerError, "korrekturstatistik lesen fehlgeschlagen")
return
}
writeJSON(w, http.StatusOK, map[string]any{
"total_corrections": st.TotalCorrections,
"top_suggested": nonNilMap(st.TopSuggested),
"top_corrected_to": nonNilMap(st.TopCorrectedTo),
"average_suggested": st.AverageSuggested,
})
}
func nonNilMap(m map[string]int) map[string]int {
if m == nil {
return map[string]int{}
}
return m
}
func (s *Server) handleEmbedIngest(w http.ResponseWriter, r *http.Request) {
var req struct {
TenantSlug string `json:"tenant_slug"`
SourceType string `json:"source_type"`
SourceID string `json:"source_id"`
Text string `json:"text"`
}
if !decode(w, r, &req) {
return
}
if strings.TrimSpace(req.TenantSlug) == "" || strings.TrimSpace(req.SourceType) == "" || strings.TrimSpace(req.SourceID) == "" {
writeError(w, http.StatusBadRequest, "tenant_slug, source_type und source_id sind erforderlich")
return
}
chunks, err := s.Embeddings.Ingest(r.Context(), req.TenantSlug, req.SourceType, req.SourceID, req.Text)
if err != nil {
writeError(w, http.StatusInternalServerError, "einreihen fehlgeschlagen")
return
}
writeJSON(w, http.StatusAccepted, map[string]int{"chunks": len(chunks)})
}
func (s *Server) handleEmbedStatus(w http.ResponseWriter, r *http.Request) {
q := r.URL.Query()
tenant, st, sid := q.Get("tenant_slug"), q.Get("source_type"), q.Get("source_id")
if tenant == "" || st == "" || sid == "" {
writeError(w, http.StatusBadRequest, "tenant_slug, source_type und source_id sind erforderlich")
return
}
rows, err := s.Embeddings.Status(r.Context(), tenant, st, sid)
if err != nil {
writeError(w, http.StatusInternalServerError, "status lesen fehlgeschlagen")
return
}
type chunk struct {
ChunkIndex int `json:"chunk_index"`
JobStatus string `json:"job_status"`
Attempts int `json:"attempts"`
LastError *string `json:"last_error"`
HasVector bool `json:"has_vector"`
}
out := make([]chunk, 0, len(rows))
for _, c := range rows {
out = append(out, chunk{c.ChunkIndex, c.JobStatus, c.Attempts, c.LastError, c.HasVector})
}
writeJSON(w, http.StatusOK, map[string]any{"chunks": out})
}
type versionJSON struct {
FunctionKey string `json:"function_key"`
Version int `json:"version"`
Body string `json:"body"`
CreatedBy string `json:"created_by"`
}
func toVersionJSON(v prompttemplate.Version) versionJSON {
return versionJSON{v.FunctionKey, v.Version, v.Body, v.CreatedBy}
}
func (s *Server) handleTemplateSave(w http.ResponseWriter, r *http.Request) {
var req struct {
Body string `json:"body"`
CreatedBy string `json:"created_by"`
}
if !decode(w, r, &req) {
return
}
if strings.TrimSpace(req.Body) == "" || strings.TrimSpace(req.CreatedBy) == "" {
writeError(w, http.StatusBadRequest, "body und created_by sind erforderlich")
return
}
v, err := s.Templates.Save(r.Context(), r.PathValue("key"), req.Body, req.CreatedBy)
if err != nil {
writeError(w, http.StatusInternalServerError, "vorlage speichern fehlgeschlagen")
return
}
writeJSON(w, http.StatusCreated, toVersionJSON(v))
}
func (s *Server) handleTemplateActive(w http.ResponseWriter, r *http.Request) {
v, err := s.Templates.Active(r.Context(), r.PathValue("key"))
if errors.Is(err, prompttemplate.ErrNotFound) {
writeError(w, http.StatusNotFound, "keine vorlage fuer diese funktion")
return
}
if err != nil {
writeError(w, http.StatusInternalServerError, "vorlage lesen fehlgeschlagen")
return
}
writeJSON(w, http.StatusOK, toVersionJSON(v))
}
func (s *Server) handleTemplateHistory(w http.ResponseWriter, r *http.Request) {
vs, err := s.Templates.History(r.Context(), r.PathValue("key"))
if err != nil {
writeError(w, http.StatusInternalServerError, "verlauf lesen fehlgeschlagen")
return
}
out := make([]versionJSON, 0, len(vs))
for _, v := range vs {
out = append(out, toVersionJSON(v))
}
writeJSON(w, http.StatusOK, map[string]any{"versions": out})
}
func (s *Server) handleTemplateActivate(w http.ResponseWriter, r *http.Request) {
var req struct {
Version int `json:"version"`
ActivatedBy string `json:"activated_by"`
}
if !decode(w, r, &req) {
return
}
if req.Version < 1 || strings.TrimSpace(req.ActivatedBy) == "" {
writeError(w, http.StatusBadRequest, "version und activated_by sind erforderlich")
return
}
err := s.Templates.Activate(r.Context(), r.PathValue("key"), req.Version, req.ActivatedBy)
if errors.Is(err, prompttemplate.ErrNotFound) {
writeError(w, http.StatusNotFound, "version unbekannt")
return
}
if err != nil {
writeError(w, http.StatusInternalServerError, "version aktivieren fehlgeschlagen")
return
}
s.handleTemplateActive(w, r)
}
func (s *Server) handleSummaryQuality(w http.ResponseWriter, r *http.Request) {
var req struct {
Samples []struct {
Type string `json:"type"`
Source string `json:"source"`
Summary string `json:"summary"`
} `json:"samples"`
}
if !decode(w, r, &req) {
return
}
if len(req.Samples) == 0 {
writeError(w, http.StatusBadRequest, "samples sind erforderlich")
return
}
type metricJSON struct {
Type string `json:"type"`
GroundednessScore float64 `json:"groundedness_score"`
Flagged bool `json:"flagged"`
Reason string `json:"reason"`
}
metrics := make([]summaryquality.Metric, 0, len(req.Samples))
items := make([]metricJSON, 0, len(req.Samples))
for _, sm := range req.Samples {
m := summaryquality.Evaluate(summaryquality.Sample{Type: sm.Type, Source: sm.Source, Summary: sm.Summary})
metrics = append(metrics, m)
items = append(items, metricJSON{sm.Type, m.GroundednessScore, m.Flagged, m.Reason})
}
type typeJSON struct {
Count int `json:"count"`
AverageGroundedness float64 `json:"average_groundedness"`
FlaggedCount int `json:"flagged_count"`
}
byType := map[string]typeJSON{}
for k, v := range summaryquality.AggregateByType(metrics) {
byType[k] = typeJSON{v.Count, v.AverageGroundedness, v.FlaggedCount}
}
writeJSON(w, http.StatusOK, map[string]any{"metrics": items, "by_type": byType})
}
func writeJSON(w http.ResponseWriter, status int, v any) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
_ = json.NewEncoder(w).Encode(v)
}
func writeError(w http.ResponseWriter, status int, msg string) {
writeJSON(w, status, map[string]string{"error": msg})
}
+387
View File
@@ -0,0 +1,387 @@
package featureapi
import (
"context"
"encoding/json"
"errors"
"net/http"
"net/http/httptest"
"strings"
"testing"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/classify"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/confidence"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/correction"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/embedpipeline"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/highlight"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/modelconfig"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/prompttemplate"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/provider"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/provider/mock"
)
type fakeFeatures struct{ off map[string]bool }
func (f *fakeFeatures) Set(_ context.Context, t, fn string, on bool) error {
f.off[t+"/"+fn] = !on
return nil
}
func (f *fakeFeatures) IsEnabled(_ context.Context, t, fn string, global bool) (bool, error) {
return global && !f.off[t+"/"+fn], nil
}
type fakeThresholds struct{ v map[string]float64 }
func (f *fakeThresholds) Set(_ context.Context, t, fn string, th float64) error {
if th < 0 || th > 1 {
return confidence.ErrInvalidThreshold
}
f.v[t+"/"+fn] = th
return nil
}
func (f *fakeThresholds) Get(_ context.Context, t, fn string) (float64, error) {
if v, ok := f.v[t+"/"+fn]; ok {
return v, nil
}
return confidence.DefaultThreshold, nil
}
func (f *fakeThresholds) MeetsThreshold(ctx context.Context, t, fn string, value float64) (bool, error) {
th, _ := f.Get(ctx, t, fn)
return value >= th, nil
}
type fakeTaxonomy struct{ codes map[string][]string }
func (f *fakeTaxonomy) List(_ context.Context, t string) ([]string, error) { return f.codes[t], nil }
func (f *fakeTaxonomy) Add(_ context.Context, t, code, _ string) error {
f.codes[t] = append(f.codes[t], code)
return nil
}
func (f *fakeTaxonomy) SeedDefaults(_ context.Context, t string) error {
f.codes[t] = []string{"rechnung", "vertrag"}
return nil
}
type fakeCorrections struct{ got []correction.Correction }
func (f *fakeCorrections) Record(_ context.Context, c correction.Correction) error {
if c.Actor == "" {
return errors.New("correction: actor darf nicht leer sein")
}
f.got = append(f.got, c)
return nil
}
func (f *fakeCorrections) Aggregate(_ context.Context, t, fn string) (correction.Stats, error) {
return correction.Stats{TenantSlug: t, Function: fn, TotalCorrections: len(f.got)}, nil
}
type fakeEmbeddings struct{}
func (fakeEmbeddings) Ingest(_ context.Context, _, _, _, text string) ([]embedpipeline.Chunk, error) {
return []embedpipeline.Chunk{{Index: 0, Text: text}, {Index: 1, Text: text}}, nil
}
func (fakeEmbeddings) Status(_ context.Context, _, _, _ string) ([]embedpipeline.ChunkStatus, error) {
return []embedpipeline.ChunkStatus{{ChunkIndex: 0, JobStatus: "done", Attempts: 1, HasVector: true}}, nil
}
type fakeModels struct {
sel map[string]modelconfig.Selection
}
func (f *fakeModels) Set(_ context.Context, t, fn, prov, model string) error {
if prov != "ollama" {
return modelconfig.ErrUnknownProvider
}
if model != "known" {
return modelconfig.ErrModelNotAvailable
}
f.sel[t+"/"+fn] = modelconfig.Selection{ProviderName: prov, Model: model}
return nil
}
func (f *fakeModels) Get(_ context.Context, t, fn string) (modelconfig.Selection, error) {
if s, ok := f.sel[t+"/"+fn]; ok {
return s, nil
}
return modelconfig.Selection{}, modelconfig.ErrNotFound
}
type fakeTemplates struct {
vs map[string][]prompttemplate.Version
}
func (f *fakeTemplates) Save(_ context.Context, key, body, by string) (prompttemplate.Version, error) {
v := prompttemplate.Version{FunctionKey: key, Version: len(f.vs[key]) + 1, Body: body, CreatedBy: by}
f.vs[key] = append(f.vs[key], v)
return v, nil
}
func (f *fakeTemplates) Activate(_ context.Context, key string, version int, _ string) error {
if version > len(f.vs[key]) {
return prompttemplate.ErrNotFound
}
f.vs[key] = append(f.vs[key][:version:version], f.vs[key][version-1])
return nil
}
func (f *fakeTemplates) Active(_ context.Context, key string) (prompttemplate.Version, error) {
if len(f.vs[key]) == 0 {
return prompttemplate.Version{}, prompttemplate.ErrNotFound
}
return f.vs[key][len(f.vs[key])-1], nil
}
func (f *fakeTemplates) History(_ context.Context, key string) ([]prompttemplate.Version, error) {
return f.vs[key], nil
}
type fixture struct {
srv *httptest.Server
prov *mock.Provider
features *fakeFeatures
corr *fakeCorrections
}
func newFixture(t *testing.T) *fixture {
t.Helper()
f := &fixture{prov: &mock.Provider{}, features: &fakeFeatures{off: map[string]bool{}}, corr: &fakeCorrections{}}
s := &Server{
Classifier: &classify.Classifier{Provider: f.prov, Model: "m"},
Highlight: &highlight.Detector{Provider: f.prov, Model: "m"},
Taxonomy: &fakeTaxonomy{codes: map[string][]string{}},
Features: f.features,
Thresholds: &fakeThresholds{v: map[string]float64{}},
Corrections: f.corr,
Embeddings: fakeEmbeddings{},
Models: &fakeModels{sel: map[string]modelconfig.Selection{}},
Templates: &fakeTemplates{vs: map[string][]prompttemplate.Version{}},
ServiceToken: "tok",
}
mux := http.NewServeMux()
s.RegisterRoutes(mux)
f.srv = httptest.NewServer(mux)
t.Cleanup(f.srv.Close)
return f
}
func (f *fixture) reply(content string) {
f.prov.ChatResponse = provider.ChatResponse{Message: provider.Message{Role: provider.RoleAssistant, Content: content}}
}
func (f *fixture) do(t *testing.T, method, path, body string, token bool) (int, map[string]any) {
t.Helper()
req, _ := http.NewRequest(method, f.srv.URL+path, strings.NewReader(body))
if token {
req.Header.Set("X-Service-Token", "tok")
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
var out map[string]any
_ = json.NewDecoder(resp.Body).Decode(&out)
return resp.StatusCode, out
}
func TestAllRoutesRequireServiceToken(t *testing.T) {
f := newFixture(t)
for _, r := range [][2]string{
{"POST", "/classify"}, {"POST", "/doctype/detect"}, {"GET", "/tenants/a/doctypes"}, {"POST", "/tenants/a/doctypes"},
{"POST", "/tenants/a/doctypes/defaults"}, {"POST", "/highlight"},
{"GET", "/tenants/a/features/classify"}, {"PUT", "/tenants/a/features/classify"},
{"GET", "/tenants/a/confidence/classify"}, {"PUT", "/tenants/a/confidence/classify"},
{"GET", "/tenants/a/model-config/chat"}, {"PUT", "/tenants/a/model-config/chat"},
{"POST", "/corrections"}, {"GET", "/tenants/a/corrections/classify/stats"},
{"POST", "/embeddings/ingest"}, {"GET", "/embeddings/status"},
{"POST", "/prompt-templates/k"}, {"GET", "/prompt-templates/k"}, {"GET", "/prompt-templates/k/history"}, {"PUT", "/prompt-templates/k/active"},
{"POST", "/summary-quality/evaluate"},
} {
if code, _ := f.do(t, r[0], r[1], `{}`, false); code != http.StatusUnauthorized {
t.Errorf("%s %s: status %d, want 401", r[0], r[1], code)
}
}
}
func TestClassify_ReturnsCategoryAndThresholdVerdict(t *testing.T) {
f := newFixture(t)
f.reply(`{"category":"rechnung","confidence":0.9,"reasoning":"x"}`)
code, out := f.do(t, "PUT", "/tenants/acme/confidence/classify", `{"threshold":0.95}`, true)
if code != http.StatusOK || out["threshold"] != 0.95 {
t.Fatalf("threshold put: %d %v", code, out)
}
code, out = f.do(t, "POST", "/classify", `{"tenant_slug":"acme","categories":["rechnung","vertrag"],"text":"Rechnung Nr. 1"}`, true)
if code != http.StatusOK || out["category"] != "rechnung" {
t.Fatalf("classify: %d %v", code, out)
}
if out["meets_threshold"] != false || out["audited"] != false {
t.Errorf("0.9 < 0.95 muss meets_threshold=false liefern, ohne Audit audited=false: %v", out)
}
}
func TestClassify_DisabledForTenantIsForbidden(t *testing.T) {
f := newFixture(t)
f.reply(`{"category":"rechnung","confidence":0.9}`)
if code, _ := f.do(t, "PUT", "/tenants/acme/features/classify", `{"enabled":false}`, true); code != http.StatusOK {
t.Fatalf("feature put: %d", code)
}
code, _ := f.do(t, "POST", "/classify", `{"tenant_slug":"acme","categories":["rechnung"],"text":"x"}`, true)
if code != http.StatusForbidden {
t.Fatalf("status %d, want 403", code)
}
if code, _ := f.do(t, "POST", "/classify", `{"tenant_slug":"other","categories":["rechnung"],"text":"x"}`, true); code != http.StatusOK {
t.Fatalf("anderer mandant: status %d, want 200", code)
}
}
func TestClassify_RejectsIncompleteRequest(t *testing.T) {
f := newFixture(t)
for _, body := range []string{`{}`, `{"tenant_slug":"a","text":"x"}`, `{"tenant_slug":"a","categories":["x"]}`, `nope`} {
if code, _ := f.do(t, "POST", "/classify", body, true); code != http.StatusBadRequest {
t.Errorf("%q: status %d, want 400", body, code)
}
}
}
func TestClassify_ProviderErrorIsBadGateway(t *testing.T) {
f := newFixture(t)
f.prov.Err = errors.New("down")
code, _ := f.do(t, "POST", "/classify", `{"tenant_slug":"a","categories":["x"],"text":"t"}`, true)
if code != http.StatusBadGateway {
t.Fatalf("status %d, want 502", code)
}
}
func TestDoctype_TaxonomyManagementAndEmptyTaxonomyConflict(t *testing.T) {
f := newFixture(t)
if code, _ := f.do(t, "POST", "/doctype/detect", `{"tenant_slug":"acme","text":"x"}`, true); code != http.StatusConflict {
t.Fatalf("leere taxonomie: status %d, want 409", code)
}
code, out := f.do(t, "POST", "/tenants/acme/doctypes/defaults", `{}`, true)
if code != http.StatusOK || len(out["doc_types"].([]any)) != 2 {
t.Fatalf("defaults: %d %v", code, out)
}
code, out = f.do(t, "POST", "/tenants/acme/doctypes", `{"code":"kuendigung","label":"Kuendigung"}`, true)
if code != http.StatusOK || len(out["doc_types"].([]any)) != 3 {
t.Fatalf("add: %d %v", code, out)
}
if code, _ := f.do(t, "POST", "/tenants/acme/doctypes", `{"code":"x"}`, true); code != http.StatusBadRequest {
t.Fatalf("add ohne label: status %d, want 400", code)
}
}
func TestHighlight_ReturnsPassagesOrEmptyList(t *testing.T) {
f := newFixture(t)
f.reply(`[]`)
code, out := f.do(t, "POST", "/highlight", `{"tenant_slug":"acme","text":"Frist endet am 1. Mai."}`, true)
if code != http.StatusOK {
t.Fatalf("status %d", code)
}
if ps, ok := out["passages"].([]any); !ok || len(ps) != 0 {
t.Errorf("passages = %v, want leere liste", out["passages"])
}
}
func TestConfidence_RejectsOutOfRange(t *testing.T) {
f := newFixture(t)
if code, _ := f.do(t, "PUT", "/tenants/a/confidence/classify", `{"threshold":1.5}`, true); code != http.StatusBadRequest {
t.Fatalf("status %d, want 400", code)
}
if code, _ := f.do(t, "PUT", "/tenants/a/confidence/classify", `{}`, true); code != http.StatusBadRequest {
t.Fatalf("ohne threshold: status %d, want 400", code)
}
_, out := f.do(t, "GET", "/tenants/a/confidence/classify", ``, true)
if out["threshold"] != confidence.DefaultThreshold {
t.Errorf("default = %v", out["threshold"])
}
}
func TestModelConfig_ValidationAndRoundTrip(t *testing.T) {
f := newFixture(t)
if code, _ := f.do(t, "GET", "/tenants/a/model-config/chat", ``, true); code != http.StatusNotFound {
t.Fatalf("ungesetzt: status %d, want 404", code)
}
if code, _ := f.do(t, "PUT", "/tenants/a/model-config/chat", `{"provider":"nope","model":"known"}`, true); code != http.StatusBadRequest {
t.Fatalf("unbekannter provider: status %d, want 400", code)
}
if code, _ := f.do(t, "PUT", "/tenants/a/model-config/chat", `{"provider":"ollama","model":"weg"}`, true); code != http.StatusBadRequest {
t.Fatalf("unbekanntes modell: status %d, want 400", code)
}
code, out := f.do(t, "PUT", "/tenants/a/model-config/chat", `{"provider":"ollama","model":"known"}`, true)
if code != http.StatusOK || out["model"] != "known" {
t.Fatalf("put: %d %v", code, out)
}
}
func TestCorrections_RecordAndStats(t *testing.T) {
f := newFixture(t)
body := `{"tenant_slug":"a","function":"classify","input_ref":"d1","suggested_category":"x","suggested_confidence":0.4,"corrected_category":"y","actor":"u1"}`
if code, _ := f.do(t, "POST", "/corrections", body, true); code != http.StatusNoContent {
t.Fatalf("record: status %d, want 204", code)
}
if code, _ := f.do(t, "POST", "/corrections", `{"tenant_slug":"a"}`, true); code != http.StatusBadRequest {
t.Fatalf("unvollstaendig: status %d, want 400", code)
}
code, out := f.do(t, "GET", "/tenants/a/corrections/classify/stats", ``, true)
if code != http.StatusOK || out["total_corrections"] != 1.0 {
t.Fatalf("stats: %d %v", code, out)
}
}
func TestEmbeddings_IngestAndStatus(t *testing.T) {
f := newFixture(t)
code, out := f.do(t, "POST", "/embeddings/ingest", `{"tenant_slug":"a","source_type":"doc","source_id":"1","text":"abc"}`, true)
if code != http.StatusAccepted || out["chunks"] != 2.0 {
t.Fatalf("ingest: %d %v", code, out)
}
if code, _ := f.do(t, "POST", "/embeddings/ingest", `{"tenant_slug":"a"}`, true); code != http.StatusBadRequest {
t.Fatalf("unvollstaendig: status %d, want 400", code)
}
code, out = f.do(t, "GET", "/embeddings/status?tenant_slug=a&source_type=doc&source_id=1", ``, true)
if code != http.StatusOK || len(out["chunks"].([]any)) != 1 {
t.Fatalf("status: %d %v", code, out)
}
if code, _ := f.do(t, "GET", "/embeddings/status", ``, true); code != http.StatusBadRequest {
t.Fatalf("ohne parameter: status %d, want 400", code)
}
}
func TestPromptTemplates_VersionsAndRollback(t *testing.T) {
f := newFixture(t)
if code, _ := f.do(t, "GET", "/prompt-templates/k", ``, true); code != http.StatusNotFound {
t.Fatalf("ohne vorlage: status %d, want 404", code)
}
f.do(t, "POST", "/prompt-templates/k", `{"body":"eins","created_by":"u"}`, true)
code, out := f.do(t, "POST", "/prompt-templates/k", `{"body":"zwei","created_by":"u"}`, true)
if code != http.StatusCreated || out["version"] != 2.0 {
t.Fatalf("save: %d %v", code, out)
}
if code, _ := f.do(t, "PUT", "/prompt-templates/k/active", `{"version":9,"activated_by":"u"}`, true); code != http.StatusNotFound {
t.Fatalf("unbekannte version: status %d, want 404", code)
}
code, out = f.do(t, "PUT", "/prompt-templates/k/active", `{"version":1,"activated_by":"u"}`, true)
if code != http.StatusOK || out["body"] != "eins" {
t.Fatalf("rollback: %d %v", code, out)
}
if code, _ := f.do(t, "POST", "/prompt-templates/k", `{"body":"x"}`, true); code != http.StatusBadRequest {
t.Fatalf("ohne created_by: status %d, want 400", code)
}
}
func TestSummaryQuality_FlagsFabricatedSummary(t *testing.T) {
f := newFixture(t)
body := `{"samples":[
{"type":"rechnung","source":"Die Rechnung ueber 100 Euro ist bis Freitag faellig","summary":"Rechnung ueber 100 Euro faellig bis Freitag"},
{"type":"rechnung","source":"Die Rechnung ueber 100 Euro ist bis Freitag faellig","summary":"Vertrag wurde fristlos gekuendigt Schadenersatz Klage erhoben"}]}`
code, out := f.do(t, "POST", "/summary-quality/evaluate", body, true)
if code != http.StatusOK {
t.Fatalf("status %d", code)
}
items := out["metrics"].([]any)
if items[0].(map[string]any)["flagged"] != false || items[1].(map[string]any)["flagged"] != true {
t.Errorf("flags = %v", items)
}
bt := out["by_type"].(map[string]any)["rechnung"].(map[string]any)
if bt["count"] != 2.0 || bt["flagged_count"] != 1.0 {
t.Errorf("by_type = %v", bt)
}
if code, _ := f.do(t, "POST", "/summary-quality/evaluate", `{"samples":[]}`, true); code != http.StatusBadRequest {
t.Fatalf("leer: status %d, want 400", code)
}
}
@@ -1,10 +1,10 @@
// Package qa03gate ist das Prüfgate für QA-03: misst Klassifizierungs- und
// Package classification ist das Prüfgate für QA-03: misst Klassifizierungs- und
// Extraktionsgenauigkeit am Referenzkorpus (QA-01, ai/internal/refcorpus)
// und belegt die Wirkung von Korrektur-Feedback auf künftige Vorschläge.
// Reines Prüfgate — ruft nur bestehende, bereits gemergte Pakete auf
// (classify CLS-02, extract EXT-05-Grundlage, correction/historysuggest
// CLS-05), ohne deren Code zu ändern.
package qa03gate
package classification
import (
"context"
@@ -1,9 +1,9 @@
// Package qa05gate ist das Prüfgate für QA-05 (Datenschutz- &
// Package governance ist das Prüfgate für QA-05 (Datenschutz- &
// Governance-Prüfung). Es bringt keine neue Fachlogik ein, sondern führt
// GOV-02/GOV-03/GOV-05 end-to-end gegen eine echte Postgres-Wegwerf-DB bzw.
// einen Mock-Core zusammen, um die drei QA-05-Akzeptanzkriterien in der
// Praxis (nicht nur auf Store-Ebene) nachzuweisen.
package qa05gate
package governance
import (
"context"
@@ -16,7 +16,6 @@ import (
"github.com/jackc/pgx/v5/pgxpool"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/auditclient"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/decisionaudit"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/migrate"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/provider"
@@ -24,7 +23,8 @@ import (
"gitea.perlbach24.de/scripte/nexarch/ai/internal/provider/openai"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/residency"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/tenantfeature"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/testdb"
"gitea.perlbach24.de/scripte/nexarch/common/auditclient"
"gitea.perlbach24.de/scripte/nexarch/common/testdb"
)
// realPool liefert einen frischen, migrierten Pool auf der Wegwerf-DB aus
@@ -36,7 +36,7 @@ func realPool(t *testing.T) *pgxpool.Pool {
t.Skip("TEST_TENANT_DSN nicht gesetzt, Integrationstest übersprungen")
}
pool := testdb.Pool(t, dsn)
migrationsDir, err := filepath.Abs("../../migrations")
migrationsDir, err := filepath.Abs("../../../migrations")
if err != nil {
t.Fatalf("migrationsverzeichnis: %v", err)
}
@@ -1,4 +1,4 @@
// Package qa04gate ist das Prüfgate für QA-04: es misst Suchqualität,
// Package searchquality ist das Prüfgate für QA-04: es misst Suchqualität,
// RAG-Antwortgüte und Quellenverweis-Nachvollziehbarkeit auf einem festen,
// synthetischen Testkorpus (SEM-01..06 bereits gemergt) und ändert keines der
// geprüften Pakete. Der Korpus stammt aus refcorpus (QA-01), die "Einbettung"
@@ -7,7 +7,7 @@
// einem externen Modell abhängt. Reine Termfrequenz ohne IDF-Gewichtung wurde
// probiert und verworfen: seltene, fachlich entscheidende Wörter (z.B.
// "Gesamtbetrag") gingen gegen häufige, generische Wörter unter.
package qa04gate
package searchquality
import (
"context"
@@ -231,4 +231,3 @@ func (p *embedProvider) Chat(ctx context.Context, req provider.ChatRequest) (pro
func (p *embedProvider) Complete(ctx context.Context, req provider.CompletionRequest) (provider.CompletionResponse, error) {
return provider.CompletionResponse{}, errors.New("qa04gate: Complete wird vom Prüfgate nicht genutzt")
}
@@ -1,4 +1,4 @@
package qa04gate
package searchquality
import (
"context"
@@ -1,4 +1,4 @@
package qa04gate
package searchquality
// testQuestion ist eine feste Testfrage des QA-04-Prüfgates gegen den
// refcorpus-Testkorpus (QA-01). erwarteteSourceID ist leer, wenn die Frage
-25
View File
@@ -1,25 +0,0 @@
// Package httpclient liefert den HTTP-Client fuer ausgehende Dienstaufrufe
// (Kopie des Musters aus connect/internal/httpclient, ENG-06): Zeitlimit
// statt endlosem Warten, keine Weiterleitungen (ein Service-Token wuerde
// sonst an das Weiterleitungsziel mitgesendet).
package httpclient
import (
"net/http"
"time"
)
const DefaultTimeout = 15 * time.Second
var shared = New(DefaultTimeout)
func Default() *http.Client { return shared }
func New(timeout time.Duration) *http.Client {
return &http.Client{
Timeout: timeout,
CheckRedirect: func(*http.Request, []*http.Request) error {
return http.ErrUseLastResponse
},
}
}
-29
View File
@@ -1,29 +0,0 @@
package httpclient
import (
"net/http"
"net/http/httptest"
"testing"
)
func TestNew_DoesNotFollowRedirects(t *testing.T) {
target := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
t.Fatal("weiterleitungsziel haette nie erreicht werden duerfen")
}))
defer target.Close()
redirector := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, target.URL, http.StatusFound)
}))
defer redirector.Close()
resp, err := Default().Get(redirector.URL)
if err != nil {
t.Fatalf("Get: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusFound {
t.Fatalf("status = %d, want %d (keine weiterleitung gefolgt)", resp.StatusCode, http.StatusFound)
}
}
-138
View File
@@ -1,138 +0,0 @@
// Package loadtest implementiert die fuer QA-07/QA-08 gemeinsam genutzte
// Last- und Leistungsmessung: parallele Sessions ausfuehren, Latenzen
// aufzeichnen, Durchsatz bestimmen. k6/vegeta sind auf diesem Rechner nicht
// installierbar (Projektregel: keine zusaetzlichen Toolchains/Dienste
// installieren) — echter Ersatz ohne externe Abhaengigkeit: reale
// nebenlaeufige Aufrufe ueber die Go-Standardbibliothek, gegen den echten,
// in dieser Sitzung gebauten Code. Eigenstaendige Kopie aus
// mail/internal/loadtest, da Go-Module keine internal/-Pakete teilen.
package loadtest
import (
"context"
"os"
"runtime"
"sort"
"strconv"
"strings"
"sync"
"time"
)
// Result fasst Latenz- und Durchsatzwerte eines Lasttest-Laufs zusammen.
type Result struct {
Protocol string
Concurrency int
Sessions int
Errors int
Duration time.Duration
Latencies []time.Duration // aufsteigend sortiert
}
// Throughput liefert Sessions pro Sekunde über die Gesamtlaufzeit.
func (r Result) Throughput() float64 {
if r.Duration <= 0 {
return 0
}
return float64(r.Sessions) / r.Duration.Seconds()
}
// Percentile liefert die Latenz am p-ten Perzentil (0.0–1.0).
func (r Result) Percentile(p float64) time.Duration {
if len(r.Latencies) == 0 {
return 0
}
idx := int(p * float64(len(r.Latencies)-1))
if idx < 0 {
idx = 0
}
if idx >= len(r.Latencies) {
idx = len(r.Latencies) - 1
}
return r.Latencies[idx]
}
// Max liefert die höchste gemessene Latenz.
func (r Result) Max() time.Duration {
if len(r.Latencies) == 0 {
return 0
}
return r.Latencies[len(r.Latencies)-1]
}
// Run führt totalSessions Sessions über maximal concurrency parallele
// Worker aus und misst je Session die Latenz von sessionFunc.
func Run(ctx context.Context, protocol string, concurrency, totalSessions int, sessionFunc func(ctx context.Context) error) Result {
var mu sync.Mutex
latencies := make([]time.Duration, 0, totalSessions)
errCount := 0
sem := make(chan struct{}, concurrency)
var wg sync.WaitGroup
start := time.Now()
for i := 0; i < totalSessions; i++ {
sem <- struct{}{}
wg.Add(1)
go func() {
defer wg.Done()
defer func() { <-sem }()
t0 := time.Now()
err := sessionFunc(ctx)
elapsed := time.Since(t0)
mu.Lock()
latencies = append(latencies, elapsed)
if err != nil {
errCount++
}
mu.Unlock()
}()
}
wg.Wait()
duration := time.Since(start)
sort.Slice(latencies, func(i, j int) bool { return latencies[i] < latencies[j] })
return Result{
Protocol: protocol,
Concurrency: concurrency,
Sessions: totalSessions,
Errors: errCount,
Duration: duration,
Latencies: latencies,
}
}
// ResourceSample ist eine Momentaufnahme des Ressourcenverbrauchs DIESES
// Prozesses: Heap über die Go-Runtime, kumulierte CPU-Zeit über
// /proc/self/stat (Linux, kein externes Werkzeug nötig).
type ResourceSample struct {
HeapAllocBytes uint64
CPUTimeSeconds float64
}
func SampleResources() ResourceSample {
var m runtime.MemStats
runtime.ReadMemStats(&m)
return ResourceSample{HeapAllocBytes: m.HeapAlloc, CPUTimeSeconds: readSelfCPUTimeSeconds()}
}
// readSelfCPUTimeSeconds liefert die kumulierte User+System-CPU-Zeit dieses
// Prozesses in Sekunden. Liefert 0, wenn /proc/self/stat nicht lesbar ist
// (z. B. auf Nicht-Linux-Systemen) — Aufrufer behandeln das als "keine
// CPU-Messung verfügbar", kein harter Fehler.
func readSelfCPUTimeSeconds() float64 {
data, err := os.ReadFile("/proc/self/stat")
if err != nil {
return 0
}
fields := strings.Fields(string(data))
if len(fields) < 15 {
return 0
}
utime, err1 := strconv.ParseFloat(fields[13], 64)
stime, err2 := strconv.ParseFloat(fields[14], 64)
if err1 != nil || err2 != nil {
return 0
}
const clockTicksPerSecond = 100 // Linux-Standard-USER_HZ
return (utime + stime) / clockTicksPerSecond
}
@@ -15,7 +15,7 @@ import (
"net/url"
"sort"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/httpclient"
"gitea.perlbach24.de/scripte/nexarch/common/httpclient"
)
type Client struct {
@@ -81,7 +81,7 @@ func TestPush_UnknownFieldKey_ReportedNotDropped(t *testing.T) {
c := New(srv.URL, "svc-token-123")
result, err := c.Push(context.Background(), "doc-1", "ai", map[string]string{
"rechnungsdatum": "2026-01-15",
"rechnungsdatum": "2026-01-15",
"nicht_existent_feld": "wert",
})
if err != nil {
+11 -205
View File
@@ -1,221 +1,27 @@
// Package migrate implements versioned, revertible SQL migrations (no ORM) — a
// standalone copy of the Core/DMS pattern, since AI is its own Go module
// and cannot import other modules' internal/ packages.
// Package migrate bindet die gemeinsame Migrationslogik aus common an den
// modulspezifischen Fortschrittstabellennamen.
package migrate
import (
"context"
"fmt"
"os"
"path/filepath"
"sort"
"strings"
"github.com/jackc/pgx/v5/pgxpool"
cmigrate "gitea.perlbach24.de/scripte/nexarch/common/migrate"
)
// Migration ist eine einzelne versionierte Migrationsdatei.
type Migration struct {
Version string // Dateiname ohne .up.sql/.down.sql, z.B. "0001_documents"
UpSQL string
DownSQL string
}
type Migration = cmigrate.Migration
// Load liest alle *.up.sql/*.down.sql-Paare aus dir, sortiert nach
// Dateiname (Akzeptanzkriterium: Migrationen sind versioniert).
func Load(dir string) ([]Migration, error) {
entries, err := os.ReadDir(dir)
if err != nil {
return nil, fmt.Errorf("migrationsverzeichnis %q lesen: %w", dir, err)
}
var versions []string
for _, e := range entries {
if e.IsDir() || !strings.HasSuffix(e.Name(), ".up.sql") {
continue
}
versions = append(versions, strings.TrimSuffix(e.Name(), ".up.sql"))
}
sort.Strings(versions)
migrations := make([]Migration, 0, len(versions))
for _, v := range versions {
up, err := os.ReadFile(filepath.Join(dir, v+".up.sql"))
if err != nil {
return nil, fmt.Errorf("migration %q: up.sql lesen: %w", v, err)
}
down, err := os.ReadFile(filepath.Join(dir, v+".down.sql"))
if err != nil {
return nil, fmt.Errorf("migration %q: down.sql lesen (jede Migration braucht ein Rollback): %w", v, err)
}
migrations = append(migrations, Migration{Version: v, UpSQL: string(up), DownSQL: string(down)})
}
return migrations, nil
}
// TrackingTable ist modulspezifisch benannt, damit sich Module in
// derselben Datenbank/demselben Schema nicht gegenseitig den
// Migrationsfortschritt ueberschreiben (FDN-13).
const TrackingTable = "connect_schema_migrations"
const legacyTrackingTable = "schema_migrations"
var migrator = cmigrate.MustNew(TrackingTable)
// ensureTrackingTable legt TrackingTable an. Beim erstmaligen Anlegen wird
// ein vorhandener Bestand aus der frueheren, unqualifizierten
// schema_migrations uebernommen (nur Versionen, die dieses Modul kennt, und
// nur wenn dessen Basisversion dort steht — eine schema_migrations eines
// anderen Moduls oder eines anderen Schemas wird weder gelesen noch
// veraendert). Die alte Tabelle
// bleibt unangetastet.
func ensureTrackingTable(ctx context.Context, pool *pgxpool.Pool, migrations []Migration) error {
tx, err := pool.Begin(ctx)
if err != nil {
return fmt.Errorf("%s anlegen: %w", TrackingTable, err)
}
defer func() { _ = tx.Rollback(ctx) }()
func Load(dir string) ([]Migration, error) { return cmigrate.Load(dir) }
// Serialisiert gleichzeitige Erstanlage/Uebernahme (mehrere App-Instanzen).
if _, err := tx.Exec(ctx, `SELECT pg_advisory_xact_lock(hashtext('connect_schema_migrations'))`); err != nil {
return fmt.Errorf("%s anlegen: %w", TrackingTable, err)
}
var existed bool
if err := tx.QueryRow(ctx, `SELECT to_regclass(current_schema() || '.' || $1::text) IS NOT NULL`, TrackingTable).Scan(&existed); err != nil {
return fmt.Errorf("%s anlegen: %w", TrackingTable, err)
}
if existed {
return tx.Commit(ctx)
}
if _, err := tx.Exec(ctx, `
CREATE TABLE `+TrackingTable+` (
version TEXT PRIMARY KEY,
applied_at TIMESTAMPTZ NOT NULL DEFAULT now()
)
`); err != nil {
return fmt.Errorf("%s anlegen: %w", TrackingTable, err)
}
if len(migrations) > 0 {
var legacy bool
if err := tx.QueryRow(ctx, `SELECT to_regclass(current_schema() || '.' || $1::text) IS NOT NULL`, legacyTrackingTable).Scan(&legacy); err != nil {
return fmt.Errorf("%s pruefen: %w", legacyTrackingTable, err)
}
if legacy {
versions := make([]string, len(migrations))
for i, m := range migrations {
versions[i] = m.Version
}
var hasBase bool
if err := tx.QueryRow(ctx,
`SELECT EXISTS (SELECT 1 FROM `+legacyTrackingTable+` WHERE version = $1)`, versions[0],
).Scan(&hasBase); err != nil {
return fmt.Errorf("%s lesen: %w", legacyTrackingTable, err)
}
if hasBase {
if _, err := tx.Exec(ctx, `
INSERT INTO `+TrackingTable+` (version, applied_at)
SELECT version, applied_at FROM `+legacyTrackingTable+` WHERE version = ANY($1)
`, versions); err != nil {
return fmt.Errorf("bestand aus %s uebernehmen: %w", legacyTrackingTable, err)
}
}
}
}
return tx.Commit(ctx)
func Up(ctx context.Context, pool *pgxpool.Pool, migrations []Migration) ([]string, error) {
return migrator.Up(ctx, pool, migrations)
}
func appliedVersions(ctx context.Context, pool *pgxpool.Pool) (map[string]bool, error) {
rows, err := pool.Query(ctx, `SELECT version FROM `+TrackingTable)
if err != nil {
return nil, fmt.Errorf("angewendete migrationen lesen: %w", err)
}
defer rows.Close()
applied := map[string]bool{}
for rows.Next() {
var v string
if err := rows.Scan(&v); err != nil {
return nil, fmt.Errorf("migrationsversion lesen: %w", err)
}
applied[v] = true
}
return applied, rows.Err()
}
// Up wendet alle noch nicht angewendeten Migrationen in Reihenfolge an
// (Akzeptanzkriterium 2: vorwaerts ausfuehrbar) — bereits angewendete
// werden uebersprungen, damit Up auf einer leeren UND auf einer bestehenden
// DB funktioniert (Pruefung 1).
func Up(ctx context.Context, pool *pgxpool.Pool, migrations []Migration) (applied []string, err error) {
if err := ensureTrackingTable(ctx, pool, migrations); err != nil {
return nil, err
}
already, err := appliedVersions(ctx, pool)
if err != nil {
return nil, err
}
for _, m := range migrations {
if already[m.Version] {
continue
}
tx, err := pool.Begin(ctx)
if err != nil {
return applied, fmt.Errorf("transaktion fuer %q starten: %w", m.Version, err)
}
if _, err := tx.Exec(ctx, m.UpSQL); err != nil {
_ = tx.Rollback(ctx)
return applied, fmt.Errorf("migration %q anwenden: %w", m.Version, err)
}
if _, err := tx.Exec(ctx, `INSERT INTO `+TrackingTable+` (version) VALUES ($1)`, m.Version); err != nil {
_ = tx.Rollback(ctx)
return applied, fmt.Errorf("migration %q als angewendet markieren: %w", m.Version, err)
}
if err := tx.Commit(ctx); err != nil {
return applied, fmt.Errorf("migration %q committen: %w", m.Version, err)
}
applied = append(applied, m.Version)
}
return applied, nil
}
// DownOne macht die zuletzt angewendete Migration rueckgaengig
// (Akzeptanzkriterium 2: rueckwaerts ausfuehrbar) und liefert deren Version,
// oder "" falls keine Migration angewendet war.
func DownOne(ctx context.Context, pool *pgxpool.Pool, migrations []Migration) (version string, err error) {
if err := ensureTrackingTable(ctx, pool, migrations); err != nil {
return "", err
}
already, err := appliedVersions(ctx, pool)
if err != nil {
return "", err
}
var last *Migration
for i := len(migrations) - 1; i >= 0; i-- {
if already[migrations[i].Version] {
last = &migrations[i]
break
}
}
if last == nil {
return "", nil
}
tx, err := pool.Begin(ctx)
if err != nil {
return "", fmt.Errorf("transaktion fuer rollback von %q starten: %w", last.Version, err)
}
if _, err := tx.Exec(ctx, last.DownSQL); err != nil {
_ = tx.Rollback(ctx)
return "", fmt.Errorf("migration %q zurueckrollen: %w", last.Version, err)
}
if _, err := tx.Exec(ctx, `DELETE FROM `+TrackingTable+` WHERE version = $1`, last.Version); err != nil {
_ = tx.Rollback(ctx)
return "", fmt.Errorf("migration %q aus %s entfernen: %w", last.Version, TrackingTable, err)
}
if err := tx.Commit(ctx); err != nil {
return "", fmt.Errorf("rollback von %q committen: %w", last.Version, err)
}
return last.Version, nil
func DownOne(ctx context.Context, pool *pgxpool.Pool, migrations []Migration) (string, error) {
return migrator.DownOne(ctx, pool, migrations)
}
+1 -1
View File
@@ -11,7 +11,7 @@ import (
"gitea.perlbach24.de/scripte/nexarch/ai/internal/migrate"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/modelconfig"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/testdb"
"gitea.perlbach24.de/scripte/nexarch/common/testdb"
)
type fakeLister struct {
+1 -1
View File
@@ -8,7 +8,7 @@ import (
"testing"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/migrate"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/testdb"
"gitea.perlbach24.de/scripte/nexarch/common/testdb"
)
// fakeVerifier simuliert Core TEN-01 fuer die Registry-Tests, ohne einen
+1 -1
View File
@@ -10,7 +10,7 @@ import (
"net/http"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/moduletype"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/svcauth"
"gitea.perlbach24.de/scripte/nexarch/common/svcauth"
)
type Server struct {
@@ -12,7 +12,7 @@ import (
"gitea.perlbach24.de/scripte/nexarch/ai/internal/migrate"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/moduletype"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/testdb"
"gitea.perlbach24.de/scripte/nexarch/common/testdb"
)
type fakeVerifier struct{ validTenants map[string]bool }
+133
View File
@@ -0,0 +1,133 @@
package originmark
import (
"context"
"errors"
"fmt"
"strings"
"unicode"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgxpool"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/decisionaudit"
)
const MaxMarkerLen = 64
var ErrInvalidMarker = errors.New("originmark: markierung muss 1 bis 64 druckbare zeichen ohne steuerzeichen haben")
type Store struct {
Pool *pgxpool.Pool
}
func validate(marker string) (string, error) {
marker = strings.TrimSpace(marker)
if marker == "" || len([]rune(marker)) > MaxMarkerLen {
return "", ErrInvalidMarker
}
for _, r := range marker {
if unicode.IsControl(r) {
return "", ErrInvalidMarker
}
}
return marker, nil
}
func (s *Store) Set(ctx context.Context, tenantSlug, marker string) error {
tenantSlug = strings.TrimSpace(tenantSlug)
if tenantSlug == "" {
return fmt.Errorf("originmark: tenantSlug darf nicht leer sein")
}
marker, err := validate(marker)
if err != nil {
return err
}
_, err = s.Pool.Exec(ctx, `
INSERT INTO ai_origin_markers (tenant_slug, marker, updated_at)
VALUES ($1, $2, now())
ON CONFLICT (tenant_slug) DO UPDATE SET marker = EXCLUDED.marker, updated_at = now()
`, tenantSlug, marker)
if err != nil {
return fmt.Errorf("originmark: speichern: %w", err)
}
return nil
}
func (s *Store) Clear(ctx context.Context, tenantSlug string) error {
if _, err := s.Pool.Exec(ctx, `DELETE FROM ai_origin_markers WHERE tenant_slug = $1`, tenantSlug); err != nil {
return fmt.Errorf("originmark: loeschen: %w", err)
}
return nil
}
// Get liefert "" ohne Konfiguration.
func (s *Store) Get(ctx context.Context, tenantSlug string) (string, error) {
var marker string
err := s.Pool.QueryRow(ctx, `SELECT marker FROM ai_origin_markers WHERE tenant_slug = $1`, tenantSlug).Scan(&marker)
if errors.Is(err, pgx.ErrNoRows) {
return "", nil
}
if err != nil {
return "", fmt.Errorf("originmark: lesen: %w", err)
}
return marker, nil
}
type Source string
const (
SourceAI Source = "ai"
SourceManual Source = "manual"
)
type Adoption struct {
Source Source
TenantSlug string
Actor string
Function string
Model string
PromptVersion int
InputRef string
Kind string
Value string
}
type MarkerReader interface {
Get(ctx context.Context, tenantSlug string) (string, error)
}
type AuditRecorder interface {
Record(ctx context.Context, d decisionaudit.Decision) error
}
type Service struct {
Markers MarkerReader
Audit AuditRecorder
}
// Apply liefert die Herkunftsmarkierung, die der Aufrufer an die uebernommene
// Zuordnung haengen soll; "" fuer manuelle Zuordnungen oder ohne Konfiguration.
// Eine KI-Uebernahme wird immer im Audit festgehalten, mit Markierung falls gesetzt.
func (s *Service) Apply(ctx context.Context, a Adoption) (string, error) {
if a.Source != SourceAI {
return "", nil
}
marker, err := s.Markers.Get(ctx, a.TenantSlug)
if err != nil {
return "", err
}
result := map[string]any{"kind": a.Kind, "value": a.Value, "origin_marker": marker}
if err := s.Audit.Record(ctx, decisionaudit.Decision{
TenantSlug: a.TenantSlug,
Actor: a.Actor,
Function: a.Function,
Model: a.Model,
PromptVersion: a.PromptVersion,
InputRef: a.InputRef,
Result: result,
}); err != nil {
return "", fmt.Errorf("originmark: audit: %w", err)
}
return marker, nil
}
+141
View File
@@ -0,0 +1,141 @@
package originmark
import (
"context"
"errors"
"os"
"path/filepath"
"strings"
"testing"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/decisionaudit"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/migrate"
"gitea.perlbach24.de/scripte/nexarch/common/testdb"
)
type fakeMarkers map[string]string
func (f fakeMarkers) Get(_ context.Context, tenant string) (string, error) { return f[tenant], nil }
type fakeAudit struct {
got []decisionaudit.Decision
err error
}
func (f *fakeAudit) Record(_ context.Context, d decisionaudit.Decision) error {
f.got = append(f.got, d)
return f.err
}
func adoption(src Source, tenant string) Adoption {
return Adoption{Source: src, TenantSlug: tenant, Actor: "u1", Function: "tagsuggest", Model: "m", PromptVersion: 1, InputRef: "doc-1", Kind: "tag", Value: "rechnung"}
}
func TestApply_AIAdoptionGetsMarkerAndAudit(t *testing.T) {
audit := &fakeAudit{}
s := &Service{Markers: fakeMarkers{"acme": "ai-processed"}, Audit: audit}
got, err := s.Apply(context.Background(), adoption(SourceAI, "acme"))
if err != nil || got != "ai-processed" {
t.Fatalf("got=%q err=%v", got, err)
}
if len(audit.got) != 1 || audit.got[0].Result["origin_marker"] != "ai-processed" || audit.got[0].Result["value"] != "rechnung" {
t.Fatalf("audit: %+v", audit.got)
}
}
func TestApply_ManualNeverMarkedNorAudited(t *testing.T) {
audit := &fakeAudit{}
s := &Service{Markers: fakeMarkers{"acme": "ai-processed"}, Audit: audit}
got, err := s.Apply(context.Background(), adoption(SourceManual, "acme"))
if err != nil || got != "" || len(audit.got) != 0 {
t.Fatalf("got=%q err=%v audit=%d", got, err, len(audit.got))
}
}
func TestApply_NoConfigNoMarkerButAudited(t *testing.T) {
audit := &fakeAudit{}
s := &Service{Markers: fakeMarkers{}, Audit: audit}
got, err := s.Apply(context.Background(), adoption(SourceAI, "acme"))
if err != nil || got != "" || len(audit.got) != 1 || audit.got[0].Result["origin_marker"] != "" {
t.Fatalf("got=%q err=%v audit=%+v", got, err, audit.got)
}
}
func TestApply_TenantIsolation(t *testing.T) {
s := &Service{Markers: fakeMarkers{"acme": "ai-processed"}, Audit: &fakeAudit{}}
got, _ := s.Apply(context.Background(), adoption(SourceAI, "other"))
if got != "" {
t.Fatalf("marker eines anderen mandanten: %q", got)
}
}
func TestApply_AuditFailureReturnsError(t *testing.T) {
s := &Service{Markers: fakeMarkers{"acme": "x"}, Audit: &fakeAudit{err: errors.New("down")}}
if got, err := s.Apply(context.Background(), adoption(SourceAI, "acme")); err == nil || got != "" {
t.Fatalf("got=%q err=%v", got, err)
}
}
func TestValidate(t *testing.T) {
for _, bad := range []string{"", " ", "a\nb", strings.Repeat("x", MaxMarkerLen+1)} {
if _, err := validate(bad); !errors.Is(err, ErrInvalidMarker) {
t.Errorf("%q: err=%v", bad, err)
}
}
if got, err := validate(" ai-processed "); err != nil || got != "ai-processed" {
t.Errorf("got=%q err=%v", got, err)
}
}
func newStore(t *testing.T) *Store {
t.Helper()
dsn := os.Getenv("TEST_TENANT_DSN")
if dsn == "" {
t.Skip("TEST_TENANT_DSN nicht gesetzt, Integrationstest übersprungen")
}
pool := testdb.Pool(t, dsn)
dir, err := filepath.Abs("../../migrations")
if err != nil {
t.Fatal(err)
}
migrations, err := migrate.Load(dir)
if err != nil {
t.Fatal(err)
}
if _, err := migrate.Up(context.Background(), pool, migrations); err != nil {
t.Fatal(err)
}
return &Store{Pool: pool}
}
func TestStore_PerTenantSetGetClear(t *testing.T) {
s := newStore(t)
ctx := context.Background()
if got, err := s.Get(ctx, "acme"); err != nil || got != "" {
t.Fatalf("ohne konfiguration: %q %v", got, err)
}
if err := s.Set(ctx, "acme", " ai-processed "); err != nil {
t.Fatal(err)
}
if err := s.Set(ctx, "other", "ki"); err != nil {
t.Fatal(err)
}
if got, _ := s.Get(ctx, "acme"); got != "ai-processed" {
t.Fatalf("acme: %q", got)
}
if got, _ := s.Get(ctx, "other"); got != "ki" {
t.Fatalf("other: %q", got)
}
if err := s.Clear(ctx, "acme"); err != nil {
t.Fatal(err)
}
if got, _ := s.Get(ctx, "acme"); got != "" {
t.Fatalf("nach clear: %q", got)
}
if got, _ := s.Get(ctx, "other"); got != "ki" {
t.Fatalf("other beruehrt: %q", got)
}
if err := s.Set(ctx, "acme", "\t"); !errors.Is(err, ErrInvalidMarker) {
t.Fatalf("err=%v", err)
}
}
-1
View File
@@ -17,7 +17,6 @@ import (
// sensitivePathPatterns beschreibt die Bereiche aus
// TESTSTRATEGIE-AI.md Abschnitt 3.
var sensitivePathPatterns = []*regexp.Regexp{
regexp.MustCompile(`(^|/)ai/internal/svcauth/`),
regexp.MustCompile(`(^|/)ai/internal/tenantfeature/`),
regexp.MustCompile(`(^|/)ai/internal/semanticsearch/`),
regexp.MustCompile(`(^|/)ai/internal/semanticsearchapi/`),
+5 -5
View File
@@ -1,24 +1,24 @@
// Negativtest des Gates selbst (QA-01 Prüfung 1): ein Diff mit
// geänderter ai/internal/svcauth/svcauth.go ohne begleitende Testdatei
// geänderter ai/internal/tenantfeature/tenantfeature.go ohne begleitende Testdatei
// muss als Verstoß erkannt werden.
package pflichttestgate
import "testing"
func TestCheckDiff_FlagsSensitiveChangeWithoutTest(t *testing.T) {
violations := CheckDiff([]string{"ai/internal/svcauth/svcauth.go"})
violations := CheckDiff([]string{"ai/internal/tenantfeature/tenantfeature.go"})
if len(violations) != 1 {
t.Fatalf("erwartet genau 1 verstoß, habe %d: %+v", len(violations), violations)
}
if violations[0].Package != "ai/internal/svcauth" {
if violations[0].Package != "ai/internal/tenantfeature" {
t.Fatalf("falsches package gemeldet: %+v", violations[0])
}
}
func TestCheckDiff_PassesWhenTestFileAccompanies(t *testing.T) {
violations := CheckDiff([]string{
"ai/internal/svcauth/svcauth.go",
"ai/internal/svcauth/svcauth_test.go",
"ai/internal/tenantfeature/tenantfeature.go",
"ai/internal/tenantfeature/tenantfeature_test.go",
})
if len(violations) != 0 {
t.Fatalf("erwartet keine verstöße, habe: %+v", violations)
+1 -1
View File
@@ -9,7 +9,7 @@ import (
"testing"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/migrate"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/testdb"
"gitea.perlbach24.de/scripte/nexarch/common/testdb"
)
func newStore(t *testing.T) *Store {
+21
View File
@@ -2,12 +2,17 @@ package provider_test
import (
"context"
"encoding/json"
"errors"
"net/http"
"net/http/httptest"
"os"
"strings"
"testing"
"time"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/provider"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/provider/gemini"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/provider/mock"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/provider/ollama"
)
@@ -84,6 +89,22 @@ func TestContract_Ollama(t *testing.T) {
runContract(t, p, model)
}
func TestContract_Gemini(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if strings.HasSuffix(r.URL.Path, ":embedContent") {
_ = json.NewEncoder(w).Encode(map[string]any{"embedding": map[string]any{"values": []float64{0.1, 0.2}}})
return
}
_ = json.NewEncoder(w).Encode(map[string]any{"candidates": []map[string]any{{"content": map[string]any{"parts": []map[string]string{{"text": "OK"}}}}}})
}))
defer srv.Close()
p, err := gemini.New(srv.URL, "k")
if err != nil {
t.Fatal(err)
}
runContract(t, p, "gemini-test")
}
// TestInterchangeable_SameCallerCode belegt AK2: ein Aufrufer, der gegen das
// Interface programmiert, funktioniert unveraendert mit jeder Implementierung.
func TestInterchangeable_SameCallerCode(t *testing.T) {
+203
View File
@@ -0,0 +1,203 @@
package gemini
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"os"
"strings"
"time"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/provider"
)
const (
DefaultBaseURL = "https://generativelanguage.googleapis.com/v1beta"
EnvAPIKey = "NEXARCH_AI_GEMINI_API_KEY"
EnvBaseURL = "NEXARCH_AI_GEMINI_BASE_URL"
)
var ErrDisabled = fmt.Errorf("gemini-adapter: %s nicht gesetzt, adapter bleibt deaktiviert", EnvAPIKey)
type Provider struct {
BaseURL string
APIKey string
Client *http.Client
}
var _ provider.Provider = (*Provider)(nil)
func New(baseURL, apiKey string) (*Provider, error) {
if apiKey == "" {
return nil, ErrDisabled
}
if baseURL == "" {
baseURL = DefaultBaseURL
}
return &Provider{
BaseURL: strings.TrimRight(baseURL, "/"),
APIKey: apiKey,
Client: &http.Client{Timeout: 120 * time.Second},
}, nil
}
func NewFromEnv() (*Provider, error) {
return New(os.Getenv(EnvBaseURL), os.Getenv(EnvAPIKey))
}
func newErr(code provider.Code, msg string, err error) error {
return &provider.Error{Provider: "gemini", Code: code, Message: msg, Err: err}
}
func (p *Provider) do(ctx context.Context, model, method string, body, out any) error {
buf, err := json.Marshal(body)
if err != nil {
return newErr(provider.CodeInvalidRequest, "anfrage kodieren", err)
}
endpoint := fmt.Sprintf("%s/models/%s:%s", p.BaseURL, url.PathEscape(strings.TrimPrefix(model, "models/")), method)
req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewReader(buf))
if err != nil {
return newErr(provider.CodeInvalidRequest, "anfrage aufbauen", err)
}
req.Header.Set("x-goog-api-key", p.APIKey)
req.Header.Set("Content-Type", "application/json")
resp, err := p.Client.Do(req)
if err != nil {
if ctx.Err() != nil {
return newErr(provider.CodeTimeout, "anfrage abgebrochen", err)
}
return newErr(provider.CodeUnavailable, "dienst nicht erreichbar", err)
}
defer resp.Body.Close()
data, err := io.ReadAll(resp.Body)
if err != nil {
return newErr(provider.CodeUpstream, "antwort lesen", err)
}
switch {
case resp.StatusCode == http.StatusNotFound:
return newErr(provider.CodeModelNotFound, "modell nicht gefunden", fmt.Errorf("http %d", resp.StatusCode))
case resp.StatusCode == http.StatusTooManyRequests:
return newErr(provider.CodeRateLimited, "rate limit", fmt.Errorf("http %d", resp.StatusCode))
case resp.StatusCode >= 400:
return newErr(provider.CodeUpstream, "fehlerhafte antwort", fmt.Errorf("http %d: %s", resp.StatusCode, string(data)))
}
if err := json.Unmarshal(data, out); err != nil {
return newErr(provider.CodeUpstream, "antwort dekodieren", err)
}
return nil
}
type part struct {
Text string `json:"text"`
}
type content struct {
Role string `json:"role,omitempty"`
Parts []part `json:"parts"`
}
type generationConfig struct {
MaxOutputTokens int `json:"maxOutputTokens,omitempty"`
Temperature float64 `json:"temperature"`
}
type generateRequest struct {
SystemInstruction *content `json:"systemInstruction,omitempty"`
Contents []content `json:"contents"`
GenerationConfig generationConfig `json:"generationConfig"`
}
type generateResponse struct {
Candidates []struct {
Content content `json:"content"`
FinishReason string `json:"finishReason"`
} `json:"candidates"`
UsageMetadata struct {
PromptTokenCount int `json:"promptTokenCount"`
CandidatesTokenCount int `json:"candidatesTokenCount"`
TotalTokenCount int `json:"totalTokenCount"`
} `json:"usageMetadata"`
ModelVersion string `json:"modelVersion"`
}
func (p *Provider) Chat(ctx context.Context, req provider.ChatRequest) (provider.ChatResponse, error) {
body := generateRequest{GenerationConfig: generationConfig{MaxOutputTokens: req.MaxTokens, Temperature: req.Temperature}}
var system []part
for _, m := range req.Messages {
switch m.Role {
case provider.RoleSystem:
system = append(system, part{Text: m.Content})
case provider.RoleAssistant:
body.Contents = append(body.Contents, content{Role: "model", Parts: []part{{Text: m.Content}}})
default:
body.Contents = append(body.Contents, content{Role: "user", Parts: []part{{Text: m.Content}}})
}
}
if len(system) > 0 {
body.SystemInstruction = &content{Parts: system}
}
var out generateResponse
if err := p.do(ctx, req.Model, "generateContent", body, &out); err != nil {
return provider.ChatResponse{}, err
}
if len(out.Candidates) == 0 {
return provider.ChatResponse{}, newErr(provider.CodeUpstream, "keine antwort in candidates", nil)
}
c := out.Candidates[0]
var text strings.Builder
for _, pt := range c.Content.Parts {
text.WriteString(pt.Text)
}
return provider.ChatResponse{
Model: out.ModelVersion,
Message: provider.Message{Role: provider.RoleAssistant, Content: text.String()},
FinishReason: c.FinishReason,
Usage: provider.Usage{
PromptTokens: out.UsageMetadata.PromptTokenCount,
CompletionTokens: out.UsageMetadata.CandidatesTokenCount,
TotalTokens: out.UsageMetadata.TotalTokenCount,
},
}, nil
}
func (p *Provider) Complete(ctx context.Context, req provider.CompletionRequest) (provider.CompletionResponse, error) {
resp, err := p.Chat(ctx, provider.ChatRequest{
Model: req.Model,
Messages: []provider.Message{{Role: provider.RoleUser, Content: req.Prompt}},
MaxTokens: req.MaxTokens,
Temperature: req.Temperature,
})
if err != nil {
return provider.CompletionResponse{}, err
}
return provider.CompletionResponse{Model: resp.Model, Text: resp.Message.Content, FinishReason: resp.FinishReason, Usage: resp.Usage}, nil
}
type embedRequest struct {
Content content `json:"content"`
}
type embedResponse struct {
Embedding struct {
Values []float64 `json:"values"`
} `json:"embedding"`
}
func (p *Provider) Embed(ctx context.Context, req provider.EmbeddingRequest) (provider.EmbeddingResponse, error) {
var out embedResponse
body := embedRequest{Content: content{Parts: []part{{Text: req.Input}}}}
if err := p.do(ctx, req.Model, "embedContent", body, &out); err != nil {
return provider.EmbeddingResponse{}, err
}
if len(out.Embedding.Values) == 0 {
return provider.EmbeddingResponse{}, newErr(provider.CodeUpstream, "keine einbettung in der antwort", nil)
}
return provider.EmbeddingResponse{Model: req.Model, Embedding: out.Embedding.Values}, nil
}
+148
View File
@@ -0,0 +1,148 @@
package gemini
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"testing"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/provider"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/provider/openai"
)
func fakeServer(t *testing.T, status int) (*httptest.Server, *map[string]any) {
t.Helper()
var last map[string]any
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if got := r.Header.Get("x-goog-api-key"); got != "key-1" {
t.Errorf("x-goog-api-key = %q", got)
}
if r.URL.Query().Get("key") != "" {
t.Error("schluessel darf nicht in der url stehen")
}
_ = json.NewDecoder(r.Body).Decode(&last)
if status != http.StatusOK {
w.WriteHeader(status)
_, _ = w.Write([]byte(`{"error":{"message":"x"}}`))
return
}
switch {
case strings.HasSuffix(r.URL.Path, ":generateContent"):
_ = json.NewEncoder(w).Encode(map[string]any{
"candidates": []map[string]any{{
"content": map[string]any{"role": "model", "parts": []map[string]string{{"text": "ha"}, {"text": "llo"}}},
"finishReason": "STOP",
}},
"usageMetadata": map[string]int{"promptTokenCount": 3, "candidatesTokenCount": 2, "totalTokenCount": 5},
"modelVersion": "gemini-test",
})
case strings.HasSuffix(r.URL.Path, ":embedContent"):
_ = json.NewEncoder(w).Encode(map[string]any{"embedding": map[string]any{"values": []float64{0.1, 0.2}}})
default:
t.Errorf("unerwarteter pfad %s", r.URL.Path)
}
}))
t.Cleanup(srv.Close)
return srv, &last
}
func TestChat_MapsRolesUsageAndParts(t *testing.T) {
srv, last := fakeServer(t, http.StatusOK)
p, _ := New(srv.URL, "key-1")
resp, err := p.Chat(context.Background(), provider.ChatRequest{
Model: "models/gemini-test",
Messages: []provider.Message{
{Role: provider.RoleSystem, Content: "sys"},
{Role: provider.RoleUser, Content: "u"},
{Role: provider.RoleAssistant, Content: "a"},
},
})
if err != nil {
t.Fatal(err)
}
if resp.Message.Content != "hallo" || resp.Usage.TotalTokens != 5 || resp.FinishReason != "STOP" {
t.Fatalf("unerwartet: %+v", resp)
}
if (*last)["systemInstruction"] == nil {
t.Fatal("systemInstruction fehlt")
}
contents := (*last)["contents"].([]any)
if len(contents) != 2 || contents[1].(map[string]any)["role"] != "model" {
t.Fatalf("rollen falsch: %v", contents)
}
}
func TestCompleteAndEmbed(t *testing.T) {
srv, _ := fakeServer(t, http.StatusOK)
p, _ := New(srv.URL, "key-1")
c, err := p.Complete(context.Background(), provider.CompletionRequest{Model: "m", Prompt: "x"})
if err != nil || c.Text != "hallo" {
t.Fatalf("complete: %+v %v", c, err)
}
e, err := p.Embed(context.Background(), provider.EmbeddingRequest{Model: "m", Input: "x"})
if err != nil || len(e.Embedding) != 2 {
t.Fatalf("embed: %+v %v", e, err)
}
}
func TestUniformErrors(t *testing.T) {
for status, code := range map[int]provider.Code{404: provider.CodeModelNotFound, 429: provider.CodeRateLimited, 500: provider.CodeUpstream} {
srv, _ := fakeServer(t, status)
p, _ := New(srv.URL, "key-1")
_, err := p.Chat(context.Background(), provider.ChatRequest{Model: "m", Messages: []provider.Message{{Role: provider.RoleUser, Content: "x"}}})
if !provider.IsCode(err, code) {
t.Errorf("status %d: err=%v, want %s", status, err, code)
}
}
}
func TestUnreachableAndCancelled(t *testing.T) {
p, _ := New("http://127.0.0.1:1", "key-1")
_, err := p.Embed(context.Background(), provider.EmbeddingRequest{Model: "m", Input: "x"})
if !provider.IsCode(err, provider.CodeUnavailable) {
t.Fatalf("err=%v", err)
}
ctx, cancel := context.WithCancel(context.Background())
cancel()
_, err = p.Embed(ctx, provider.EmbeddingRequest{Model: "m", Input: "x"})
if !provider.IsCode(err, provider.CodeTimeout) {
t.Fatalf("err=%v", err)
}
}
func TestDisabledWithoutKey(t *testing.T) {
t.Setenv(EnvAPIKey, "")
if _, err := NewFromEnv(); err != ErrDisabled {
t.Fatalf("err=%v", err)
}
}
func TestKeyNotInErrors(t *testing.T) {
srv, _ := fakeServer(t, http.StatusInternalServerError)
p, _ := New(srv.URL, "key-1")
_, err := p.Chat(context.Background(), provider.ChatRequest{Model: "m", Messages: []provider.Message{{Role: provider.RoleUser, Content: "x"}}})
if err == nil || strings.Contains(err.Error(), "key-1") {
t.Fatalf("err=%v", err)
}
}
func TestOptIn_GuardedProviderBlocksWithoutCallingServer(t *testing.T) {
called := false
srv := httptest.NewServer(http.HandlerFunc(func(http.ResponseWriter, *http.Request) { called = true }))
defer srv.Close()
inner, _ := New(srv.URL, "key-1")
g := &openai.GuardedProvider{Inner: inner, Gate: openai.NewEnvTenantGate("acme")}
req := provider.ChatRequest{Model: "m", Messages: []provider.Message{{Role: provider.RoleUser, Content: "x"}}}
if _, err := g.Chat(openai.WithTenant(context.Background(), "other"), req); err == nil {
t.Fatal("freigabe fehlt, fehler erwartet")
}
if _, err := g.Chat(context.Background(), req); err == nil {
t.Fatal("ohne mandant, fehler erwartet")
}
if called {
t.Fatal("server wurde trotz fehlender freigabe aufgerufen")
}
}
+1 -1
View File
@@ -8,8 +8,8 @@ import (
"time"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/limits"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/loadtest"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/provider"
"gitea.perlbach24.de/scripte/nexarch/common/loadtest"
)
// QA-08 Pruefung 2 (AK2): realistische parallele Klassifizierungs-/
+1 -1
View File
@@ -14,7 +14,7 @@ import (
"gitea.perlbach24.de/scripte/nexarch/ai/internal/migrate"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/provider"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/testdb"
"gitea.perlbach24.de/scripte/nexarch/common/testdb"
)
func newPool(t *testing.T) *pgxpool.Pool {
+3 -3
View File
@@ -16,9 +16,9 @@ import (
// eine Zeitueberschreitung real simulieren laesst (respektiert ctx wie ein
// echter HTTP-Client es tun wuerde).
type slowProvider struct {
delay time.Duration
calls atomic.Int64
cancelled atomic.Int64
delay time.Duration
calls atomic.Int64
cancelled atomic.Int64
}
func (s *slowProvider) Chat(ctx context.Context, req provider.ChatRequest) (provider.ChatResponse, error) {
+1 -1
View File
@@ -11,7 +11,7 @@ import (
"github.com/jackc/pgx/v5/pgxpool"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/migrate"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/testdb"
"gitea.perlbach24.de/scripte/nexarch/common/testdb"
)
func newPool(t *testing.T) *pgxpool.Pool {
+1 -1
View File
@@ -9,7 +9,7 @@ import (
"gitea.perlbach24.de/scripte/nexarch/ai/internal/migrate"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/provider/openai"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/residency"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/testdb"
"gitea.perlbach24.de/scripte/nexarch/common/testdb"
)
func setup(t *testing.T) *residency.Store {
+1 -1
View File
@@ -9,7 +9,7 @@ import (
"net/http"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/residency"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/svcauth"
"gitea.perlbach24.de/scripte/nexarch/common/svcauth"
)
type Server struct {
@@ -13,7 +13,7 @@ import (
"gitea.perlbach24.de/scripte/nexarch/ai/internal/migrate"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/residency"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/residencyapi"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/testdb"
"gitea.perlbach24.de/scripte/nexarch/common/testdb"
)
const testToken = "test-service-token"
@@ -8,7 +8,7 @@ import (
"gitea.perlbach24.de/scripte/nexarch/ai/internal/historysuggest"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/migrate"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/testdb"
"gitea.perlbach24.de/scripte/nexarch/common/testdb"
)
func newStore(t *testing.T) *Store {
@@ -13,7 +13,7 @@ import (
"gitea.perlbach24.de/scripte/nexarch/ai/internal/historysuggest"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/routingfeedback"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/svcauth"
"gitea.perlbach24.de/scripte/nexarch/common/svcauth"
)
type Server struct {
@@ -11,7 +11,7 @@ import (
"gitea.perlbach24.de/scripte/nexarch/ai/internal/migrate"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/routingfeedback"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/testdb"
"gitea.perlbach24.de/scripte/nexarch/common/testdb"
)
const testToken = "test-service-token"
@@ -11,8 +11,8 @@ import (
"gitea.perlbach24.de/scripte/nexarch/ai/internal/migrate"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/semanticsearch"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/testdb"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/vectorindex"
"gitea.perlbach24.de/scripte/nexarch/common/testdb"
)
func newRealPool(t *testing.T) *pgxpool.Pool {
@@ -1,7 +1,8 @@
// Package semanticsearchapi ist die HTTP-Bindung der semantischen Suche
// (SEM-03). Aufrufer ist das anfragende Modul (DMS/Mail), nicht der
// Endnutzer direkt — Service-Token-Pflicht wie moduleadapter (Workflow
// ENG-02), fail closed.
// ENG-02), fail closed. Zusaetzlich (Issue #369) Rechtepruefung
// ai.search.read gegen Core fuer den Mandanten aus dem Anfrage-Koerper.
package semanticsearchapi
import (
@@ -10,18 +11,30 @@ import (
"net/http"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/semanticsearch"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/svcauth"
"gitea.perlbach24.de/scripte/nexarch/common/policyclient"
"gitea.perlbach24.de/scripte/nexarch/common/svcauth"
)
const permissionSearchRead = "ai.search.read"
type Server struct {
svc *semanticsearch.Service
serviceToken string
policy *policyclient.Client
}
func NewServer(svc *semanticsearch.Service, serviceToken string) *Server {
return &Server{svc: svc, serviceToken: serviceToken}
}
// WithPolicy aktiviert die Rechtepruefung gegen Core (POST /authorize).
// Rolle ist fest "user", weil der Modul-zu-Modul-Aufruf keine Nutzerrolle
// transportiert (Uebergang, gleiches Muster wie connect/internal/statusapi).
func (s *Server) WithPolicy(client *policyclient.Client) *Server {
s.policy = client
return s
}
func (s *Server) RegisterRoutes(mux *http.ServeMux) {
mux.HandleFunc("POST /search", s.requireAuth(s.handleSearch))
}
@@ -37,6 +50,22 @@ func (s *Server) requireAuth(next http.HandlerFunc) http.HandlerFunc {
}
}
func (s *Server) permitted(w http.ResponseWriter, r *http.Request, tenantSlug, permission string) bool {
if s.policy == nil {
return true
}
allowed, err := s.policy.Allowed(r.Context(), tenantSlug, "user", permission)
if err != nil {
writeError(w, http.StatusServiceUnavailable, "berechtigungsdienst nicht erreichbar")
return false
}
if !allowed {
writeError(w, http.StatusForbidden, "keine berechtigung")
return false
}
return true
}
type refRequest struct {
SourceType string `json:"source_type"`
SourceID string `json:"source_id"`
@@ -59,6 +88,9 @@ func (s *Server) handleSearch(w http.ResponseWriter, r *http.Request) {
writeError(w, http.StatusBadRequest, "ungueltiger anfrage-koerper")
return
}
if !s.permitted(w, r, req.TenantSlug, permissionSearchRead) {
return
}
refs := make([]semanticsearch.Ref, 0, len(req.AllowedRefs))
for _, ref := range req.AllowedRefs {
@@ -0,0 +1,100 @@
package semanticsearchapi_test
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"gitea.perlbach24.de/scripte/nexarch/common/policyclient"
)
// Steht fuer Core's POST /authorize-Vertrag ({"allowed": bool}); prueft
// die echte Kette requireAuth -> permitted -> policyclient -> 403/200.
func newFakePolicyAPI(t *testing.T, allow bool, gotBody *map[string]string) *httptest.Server {
t.Helper()
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/authorize" || r.Header.Get("X-Service-Token") == "" {
t.Fatalf("unerwartete anfrage: %s %s", r.Method, r.URL.Path)
}
if gotBody != nil {
_ = json.NewDecoder(r.Body).Decode(gotBody)
}
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(map[string]bool{"allowed": allow})
}))
}
func searchRequest(t *testing.T) *http.Request {
t.Helper()
body, _ := json.Marshal(map[string]any{
"tenant_slug": "acme",
"query": "test",
"top_k": 5,
"allowed_refs": []map[string]string{{"source_type": "dms_document", "source_id": "doc-1"}},
})
req := httptest.NewRequest(http.MethodPost, "/search", bytes.NewReader(body))
req.Header.Set("X-Service-Token", "geheimes-token")
return req
}
func TestHandleSearch_PolicyDenies_Rejected(t *testing.T) {
var got map[string]string
policyAPI := newFakePolicyAPI(t, false, &got)
defer policyAPI.Close()
mux := http.NewServeMux()
newTestServer().WithPolicy(policyclient.New(policyAPI.URL, "test-token")).RegisterRoutes(mux)
rec := httptest.NewRecorder()
mux.ServeHTTP(rec, searchRequest(t))
if rec.Code != http.StatusForbidden {
t.Fatalf("erwartet 403 ohne berechtigung, bekam %d: %s", rec.Code, rec.Body.String())
}
if got["permission"] != "ai.search.read" || got["tenant"] != "acme" {
t.Fatalf("falsche policy-anfrage: %+v", got)
}
}
func TestHandleSearch_PolicyAllows_Passes(t *testing.T) {
policyAPI := newFakePolicyAPI(t, true, nil)
defer policyAPI.Close()
mux := http.NewServeMux()
newTestServer().WithPolicy(policyclient.New(policyAPI.URL, "test-token")).RegisterRoutes(mux)
rec := httptest.NewRecorder()
mux.ServeHTTP(rec, searchRequest(t))
if rec.Code != http.StatusOK {
t.Fatalf("erwartet 200 mit berechtigung, bekam %d: %s", rec.Code, rec.Body.String())
}
}
func TestHandleSearch_PolicyUnavailable_FailsClosed(t *testing.T) {
policyAPI := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
}))
defer policyAPI.Close()
mux := http.NewServeMux()
newTestServer().WithPolicy(policyclient.New(policyAPI.URL, "test-token")).RegisterRoutes(mux)
rec := httptest.NewRecorder()
mux.ServeHTTP(rec, searchRequest(t))
if rec.Code != http.StatusServiceUnavailable {
t.Fatalf("erwartet 503 bei ausfall des berechtigungsdienstes, bekam %d", rec.Code)
}
}
func TestHandleSearch_PolicyCheckedOnlyAfterServiceToken(t *testing.T) {
policyAPI := newFakePolicyAPI(t, true, nil)
defer policyAPI.Close()
mux := http.NewServeMux()
newTestServer().WithPolicy(policyclient.New(policyAPI.URL, "test-token")).RegisterRoutes(mux)
req := searchRequest(t)
req.Header.Set("X-Service-Token", "falsch")
rec := httptest.NewRecorder()
mux.ServeHTTP(rec, req)
if rec.Code != http.StatusUnauthorized {
t.Fatalf("erwartet 401 bei falschem service-token, bekam %d", rec.Code)
}
}
+293
View File
@@ -0,0 +1,293 @@
package suggestapi
import (
"context"
"encoding/json"
"errors"
"net/http"
"strings"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/correspondentsuggest"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/doclang"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/originmark"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/provider"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/tagsuggest"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/titlesuggest"
"gitea.perlbach24.de/scripte/nexarch/common/svcauth"
)
const maxBodyBytes = 1 << 20
type MarkerStore interface {
Set(ctx context.Context, tenantSlug, marker string) error
Get(ctx context.Context, tenantSlug string) (string, error)
Clear(ctx context.Context, tenantSlug string) error
}
type LanguageStore interface {
Set(ctx context.Context, tenantSlug, code string) error
Get(ctx context.Context, tenantSlug string) (string, error)
}
type Server struct {
Provider provider.Provider
Model string
Resolver *doclang.Resolver
Languages LanguageStore
Markers MarkerStore
Adopt *originmark.Service
ServiceToken string
}
func (s *Server) RegisterRoutes(mux *http.ServeMux) {
mux.HandleFunc("POST /suggest/title", s.auth(s.handleTitle))
mux.HandleFunc("POST /suggest/correspondent", s.auth(s.handleCorrespondent))
mux.HandleFunc("POST /suggest/tags", s.auth(s.handleTags))
mux.HandleFunc("POST /suggest/adopt", s.auth(s.handleAdopt))
mux.HandleFunc("GET /tenants/{tenant}/origin-marker", s.auth(s.handleMarkerGet))
mux.HandleFunc("PUT /tenants/{tenant}/origin-marker", s.auth(s.handleMarkerPut))
mux.HandleFunc("DELETE /tenants/{tenant}/origin-marker", s.auth(s.handleMarkerDelete))
mux.HandleFunc("GET /tenants/{tenant}/language", s.auth(s.handleLanguageGet))
mux.HandleFunc("PUT /tenants/{tenant}/language", s.auth(s.handleLanguagePut))
}
func (s *Server) auth(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if !svcauth.Equal(r.Header.Get("X-Service-Token"), s.ServiceToken) {
writeError(w, http.StatusUnauthorized, "service-token fehlt oder ist ungueltig")
return
}
next(w, r)
}
}
type suggestRequest struct {
TenantSlug string `json:"tenant_slug"`
Module string `json:"module"`
Text string `json:"text"`
DocumentLanguage string `json:"document_language"`
Existing []string `json:"existing"`
}
type languages struct {
Document string `json:"document_language,omitempty"`
Output string `json:"output_language,omitempty"`
}
// prepare dekodiert und prueft die Anfrage und loest die Sprachen auf; bei Fehler ist die Antwort geschrieben.
func (s *Server) prepare(w http.ResponseWriter, r *http.Request) (suggestRequest, doclang.Resolved, bool) {
var req suggestRequest
if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, maxBodyBytes)).Decode(&req); err != nil {
writeError(w, http.StatusBadRequest, "ungueltiger anfrage-koerper")
return req, doclang.Resolved{}, false
}
if strings.TrimSpace(req.TenantSlug) == "" || strings.TrimSpace(req.Text) == "" {
writeError(w, http.StatusBadRequest, "tenant_slug und text sind erforderlich")
return req, doclang.Resolved{}, false
}
res, err := s.Resolver.Resolve(r.Context(), req.TenantSlug, req.DocumentLanguage, req.Text)
if errors.Is(err, doclang.ErrUnknownLanguage) {
writeError(w, http.StatusBadRequest, "document_language ist nicht registriert")
return req, res, false
}
if err != nil {
writeError(w, http.StatusInternalServerError, "sprache ermitteln fehlgeschlagen")
return req, res, false
}
return req, res, true
}
func langsOf(res doclang.Resolved) languages {
return languages{Document: res.Document.Code, Output: res.Output.Code}
}
type titleResponse struct {
languages
Titles []titlesuggest.Suggestion `json:"titles"`
}
func (s *Server) handleTitle(w http.ResponseWriter, r *http.Request) {
req, res, ok := s.prepare(w, r)
if !ok {
return
}
sug := &titlesuggest.Suggester{Provider: s.Provider, Model: s.Model}
titles, err := sug.Suggest(r.Context(), req.Module, res.Output.Name, req.Text)
if err != nil {
writeError(w, http.StatusBadGateway, "titelvorschlag fehlgeschlagen")
return
}
if titles == nil {
titles = []titlesuggest.Suggestion{}
}
writeJSON(w, http.StatusOK, titleResponse{languages: langsOf(res), Titles: titles})
}
type correspondentResponse struct {
languages
Suggestion *correspondentsuggest.Suggestion `json:"suggestion"`
}
func (s *Server) handleCorrespondent(w http.ResponseWriter, r *http.Request) {
req, res, ok := s.prepare(w, r)
if !ok {
return
}
sug := &correspondentsuggest.Suggester{Provider: s.Provider, Model: s.Model}
got, found, err := sug.Suggest(r.Context(), req.Existing, req.Module, req.Text)
if err != nil {
writeError(w, http.StatusBadGateway, "korrespondentenvorschlag fehlgeschlagen")
return
}
resp := correspondentResponse{languages: langsOf(res)}
if found {
resp.Suggestion = &got
}
writeJSON(w, http.StatusOK, resp)
}
type tagsResponse struct {
languages
Tags []tagsuggest.Suggestion `json:"tags"`
}
func (s *Server) handleTags(w http.ResponseWriter, r *http.Request) {
req, res, ok := s.prepare(w, r)
if !ok {
return
}
sug := &tagsuggest.Suggester{Provider: s.Provider, Model: s.Model, Language: res.Output.Name}
tags, err := sug.Suggest(r.Context(), req.Existing, req.Module, req.Text)
if err != nil {
writeError(w, http.StatusBadGateway, "tagvorschlag fehlgeschlagen")
return
}
if tags == nil {
tags = []tagsuggest.Suggestion{}
}
writeJSON(w, http.StatusOK, tagsResponse{languages: langsOf(res), Tags: tags})
}
type adoptRequest struct {
Source string `json:"source"`
TenantSlug string `json:"tenant_slug"`
Actor string `json:"actor"`
Function string `json:"function"`
Model string `json:"model"`
PromptVersion int `json:"prompt_version"`
InputRef string `json:"input_ref"`
Kind string `json:"kind"`
Value string `json:"value"`
}
func (s *Server) handleAdopt(w http.ResponseWriter, r *http.Request) {
if s.Adopt == nil {
writeError(w, http.StatusServiceUnavailable, "uebernahme-protokollierung nicht konfiguriert")
return
}
var req adoptRequest
if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, maxBodyBytes)).Decode(&req); err != nil {
writeError(w, http.StatusBadRequest, "ungueltiger anfrage-koerper")
return
}
src := originmark.Source(req.Source)
if src != originmark.SourceAI && src != originmark.SourceManual {
writeError(w, http.StatusBadRequest, "source muss ai oder manual sein")
return
}
if strings.TrimSpace(req.TenantSlug) == "" || strings.TrimSpace(req.Kind) == "" || strings.TrimSpace(req.Value) == "" {
writeError(w, http.StatusBadRequest, "tenant_slug, kind und value sind erforderlich")
return
}
if src == originmark.SourceAI && (req.Function == "" || req.Model == "" || req.InputRef == "") {
writeError(w, http.StatusBadRequest, "function, model und input_ref sind bei source=ai erforderlich")
return
}
marker, err := s.Adopt.Apply(r.Context(), originmark.Adoption{
Source: src, TenantSlug: req.TenantSlug, Actor: req.Actor, Function: req.Function, Model: req.Model,
PromptVersion: req.PromptVersion, InputRef: req.InputRef, Kind: req.Kind, Value: req.Value,
})
if err != nil {
writeError(w, http.StatusBadGateway, "uebernahme protokollieren fehlgeschlagen")
return
}
writeJSON(w, http.StatusOK, map[string]string{"origin_marker": marker})
}
func (s *Server) handleMarkerGet(w http.ResponseWriter, r *http.Request) {
marker, err := s.Markers.Get(r.Context(), r.PathValue("tenant"))
if err != nil {
writeError(w, http.StatusInternalServerError, "markierung lesen fehlgeschlagen")
return
}
writeJSON(w, http.StatusOK, map[string]string{"marker": marker})
}
func (s *Server) handleMarkerPut(w http.ResponseWriter, r *http.Request) {
var req struct {
Marker string `json:"marker"`
}
if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, maxBodyBytes)).Decode(&req); err != nil {
writeError(w, http.StatusBadRequest, "ungueltiger anfrage-koerper")
return
}
if err := s.Markers.Set(r.Context(), r.PathValue("tenant"), req.Marker); err != nil {
if errors.Is(err, originmark.ErrInvalidMarker) {
writeError(w, http.StatusBadRequest, err.Error())
return
}
writeError(w, http.StatusInternalServerError, "markierung speichern fehlgeschlagen")
return
}
s.handleMarkerGet(w, r)
}
func (s *Server) handleMarkerDelete(w http.ResponseWriter, r *http.Request) {
if err := s.Markers.Clear(r.Context(), r.PathValue("tenant")); err != nil {
writeError(w, http.StatusInternalServerError, "markierung loeschen fehlgeschlagen")
return
}
s.handleMarkerGet(w, r)
}
func (s *Server) handleLanguageGet(w http.ResponseWriter, r *http.Request) {
code, err := s.Languages.Get(r.Context(), r.PathValue("tenant"))
if err != nil {
writeError(w, http.StatusInternalServerError, "sprache lesen fehlgeschlagen")
return
}
writeJSON(w, http.StatusOK, map[string]string{"language": code})
}
func (s *Server) handleLanguagePut(w http.ResponseWriter, r *http.Request) {
var req struct {
Language string `json:"language"`
}
if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, maxBodyBytes)).Decode(&req); err != nil {
writeError(w, http.StatusBadRequest, "ungueltiger anfrage-koerper")
return
}
if _, ok := s.Resolver.Registry.Lookup(req.Language); !ok {
writeError(w, http.StatusBadRequest, "sprache nicht registriert")
return
}
if err := s.Languages.Set(r.Context(), r.PathValue("tenant"), req.Language); err != nil {
if errors.Is(err, doclang.ErrInvalidCode) {
writeError(w, http.StatusBadRequest, err.Error())
return
}
writeError(w, http.StatusInternalServerError, "sprache speichern fehlgeschlagen")
return
}
s.handleLanguageGet(w, r)
}
func writeJSON(w http.ResponseWriter, status int, v any) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
_ = json.NewEncoder(w).Encode(v)
}
func writeError(w http.ResponseWriter, status int, message string) {
writeJSON(w, status, map[string]string{"error": message})
}
+241
View File
@@ -0,0 +1,241 @@
package suggestapi
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"testing"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/decisionaudit"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/doclang"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/originmark"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/provider"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/provider/mock"
)
type memStore struct{ m map[string]string }
func newMem() *memStore { return &memStore{m: map[string]string{}} }
func (s *memStore) Set(_ context.Context, tenant, v string) error {
if strings.TrimSpace(v) == "" {
return originmark.ErrInvalidMarker
}
s.m[tenant] = v
return nil
}
func (s *memStore) Get(_ context.Context, tenant string) (string, error) { return s.m[tenant], nil }
func (s *memStore) Clear(_ context.Context, tenant string) error { delete(s.m, tenant); return nil }
type recAudit struct{ got []decisionaudit.Decision }
func (r *recAudit) Record(_ context.Context, d decisionaudit.Decision) error {
r.got = append(r.got, d)
return nil
}
type fixture struct {
srv *httptest.Server
markers *memStore
langs *memStore
audit *recAudit
prov *mock.Provider
}
func newFixture(t *testing.T, withAdopt bool) *fixture {
t.Helper()
f := &fixture{markers: newMem(), langs: newMem(), audit: &recAudit{}, prov: &mock.Provider{}}
s := &Server{
Provider: f.prov,
Model: "m",
Resolver: &doclang.Resolver{Registry: doclang.DefaultRegistry(), Tenants: tenantLang{f.langs}},
Languages: f.langs,
Markers: f.markers,
ServiceToken: "tok",
}
if withAdopt {
s.Adopt = &originmark.Service{Markers: f.markers, Audit: f.audit}
}
mux := http.NewServeMux()
s.RegisterRoutes(mux)
f.srv = httptest.NewServer(mux)
t.Cleanup(f.srv.Close)
return f
}
type tenantLang struct{ s *memStore }
func (t tenantLang) Get(ctx context.Context, tenant string) (string, error) {
return t.s.Get(ctx, tenant)
}
func (f *fixture) reply(content string) {
f.prov.ChatResponse = provider.ChatResponse{Message: provider.Message{Role: provider.RoleAssistant, Content: content}}
}
func (f *fixture) do(t *testing.T, method, path, body string, token bool) (int, map[string]any) {
t.Helper()
req, _ := http.NewRequest(method, f.srv.URL+path, strings.NewReader(body))
if token {
req.Header.Set("X-Service-Token", "tok")
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
var out map[string]any
_ = json.NewDecoder(resp.Body).Decode(&out)
return resp.StatusCode, out
}
const enDoc = "Dear Sirs, the invoice is not paid and will be sent to you by the reminder that we have prepared for your account."
func TestAllRoutesRequireServiceToken(t *testing.T) {
f := newFixture(t, true)
for _, r := range [][2]string{
{"POST", "/suggest/title"}, {"POST", "/suggest/correspondent"}, {"POST", "/suggest/tags"}, {"POST", "/suggest/adopt"},
{"GET", "/tenants/acme/origin-marker"}, {"PUT", "/tenants/acme/origin-marker"}, {"DELETE", "/tenants/acme/origin-marker"},
{"GET", "/tenants/acme/language"}, {"PUT", "/tenants/acme/language"},
} {
if code, _ := f.do(t, r[0], r[1], `{}`, false); code != http.StatusUnauthorized {
t.Errorf("%s %s: status %d, want 401", r[0], r[1], code)
}
}
}
func TestTitle_ReturnsTitlesAndResolvedLanguages(t *testing.T) {
f := newFixture(t, false)
f.langs.m["acme"] = "de"
f.reply(`[{"title":"Zahlungserinnerung","confidence":0.9}]`)
code, out := f.do(t, "POST", "/suggest/title", `{"tenant_slug":"acme","module":"dms","text":"`+enDoc+`"}`, true)
if code != 200 || out["document_language"] != "en" || out["output_language"] != "de" {
t.Fatalf("%d %v", code, out)
}
titles := out["titles"].([]any)
if len(titles) != 1 || titles[0].(map[string]any)["title"] != "Zahlungserinnerung" {
t.Fatalf("%v", out)
}
}
func TestTitle_EmptyListNotNull(t *testing.T) {
f := newFixture(t, false)
f.reply("kein json")
_, out := f.do(t, "POST", "/suggest/title", `{"tenant_slug":"acme","text":"x"}`, true)
if titles, ok := out["titles"].([]any); !ok || len(titles) != 0 {
t.Fatalf("%v", out)
}
}
func TestCorrespondent_FoundAndNone(t *testing.T) {
f := newFixture(t, false)
f.reply(`{"name":"muster gmbh","confidence":0.8}`)
_, out := f.do(t, "POST", "/suggest/correspondent", `{"tenant_slug":"acme","text":"Brief","existing":["Muster GmbH"]}`, true)
s := out["suggestion"].(map[string]any)
if s["name"] != "Muster GmbH" || s["existing"] != true {
t.Fatalf("%v", out)
}
f.reply(`{"name":"","confidence":0}`)
_, out = f.do(t, "POST", "/suggest/correspondent", `{"tenant_slug":"acme","text":"Brief"}`, true)
if out["suggestion"] != nil {
t.Fatalf("%v", out)
}
}
func TestTags_NormalizesToExisting(t *testing.T) {
f := newFixture(t, false)
f.reply(`[{"tag":"rechnung","confidence":0.9}]`)
_, out := f.do(t, "POST", "/suggest/tags", `{"tenant_slug":"acme","text":"x","existing":["Rechnung"]}`, true)
tags := out["tags"].([]any)
if len(tags) != 1 || tags[0].(map[string]any)["tag"] != "Rechnung" {
t.Fatalf("%v", out)
}
}
func TestSuggest_BadRequests(t *testing.T) {
f := newFixture(t, false)
for name, body := range map[string]string{
"kein json": `{`,
"ohne tenant": `{"text":"x"}`,
"ohne text": `{"tenant_slug":"acme","text":" "}`,
"unbekannt lang": `{"tenant_slug":"acme","text":"x","document_language":"xx"}`,
} {
if code, _ := f.do(t, "POST", "/suggest/title", body, true); code != http.StatusBadRequest {
t.Errorf("%s: status %d, want 400", name, code)
}
}
}
func TestSuggest_ProviderErrorIs502(t *testing.T) {
f := newFixture(t, false)
f.prov.Err = &provider.Error{Provider: "mock", Code: provider.CodeUnavailable, Message: "down"}
for _, p := range []string{"title", "correspondent", "tags"} {
if code, _ := f.do(t, "POST", "/suggest/"+p, `{"tenant_slug":"acme","text":"x"}`, true); code != http.StatusBadGateway {
t.Errorf("%s: status %d, want 502", p, code)
}
}
}
func TestAdopt_AIGetsMarkerManualDoesNot(t *testing.T) {
f := newFixture(t, true)
f.markers.m["acme"] = "ai-processed"
ai := `{"source":"ai","tenant_slug":"acme","actor":"u1","function":"tagsuggest","model":"m","prompt_version":1,"input_ref":"doc-1","kind":"tag","value":"rechnung"}`
code, out := f.do(t, "POST", "/suggest/adopt", ai, true)
if code != 200 || out["origin_marker"] != "ai-processed" || len(f.audit.got) != 1 {
t.Fatalf("%d %v audit=%d", code, out, len(f.audit.got))
}
manual := `{"source":"manual","tenant_slug":"acme","kind":"tag","value":"rechnung"}`
code, out = f.do(t, "POST", "/suggest/adopt", manual, true)
if code != 200 || out["origin_marker"] != "" || len(f.audit.got) != 1 {
t.Fatalf("%d %v audit=%d", code, out, len(f.audit.got))
}
}
func TestAdopt_ValidationAndUnconfigured(t *testing.T) {
f := newFixture(t, true)
for name, body := range map[string]string{
"source falsch": `{"source":"x","tenant_slug":"a","kind":"tag","value":"v"}`,
"ai ohne input": `{"source":"ai","tenant_slug":"a","kind":"tag","value":"v","function":"f","model":"m"}`,
"ohne value": `{"source":"manual","tenant_slug":"a","kind":"tag"}`,
"kein json": `{`,
} {
if code, _ := f.do(t, "POST", "/suggest/adopt", body, true); code != http.StatusBadRequest {
t.Errorf("%s: status %d, want 400", name, code)
}
}
g := newFixture(t, false)
if code, _ := g.do(t, "POST", "/suggest/adopt", `{"source":"manual","tenant_slug":"a","kind":"tag","value":"v"}`, true); code != http.StatusServiceUnavailable {
t.Errorf("status %d, want 503", code)
}
}
func TestOriginMarkerConfig(t *testing.T) {
f := newFixture(t, false)
if code, out := f.do(t, "PUT", "/tenants/acme/origin-marker", `{"marker":"ki"}`, true); code != 200 || out["marker"] != "ki" {
t.Fatalf("%d %v", code, out)
}
if _, out := f.do(t, "GET", "/tenants/other/origin-marker", "", true); out["marker"] != "" {
t.Fatalf("mandantentrennung: %v", out)
}
if code, _ := f.do(t, "PUT", "/tenants/acme/origin-marker", `{"marker":" "}`, true); code != http.StatusBadRequest {
t.Fatalf("status %d, want 400", code)
}
if code, out := f.do(t, "DELETE", "/tenants/acme/origin-marker", "", true); code != 200 || out["marker"] != "" {
t.Fatalf("%d %v", code, out)
}
}
func TestLanguageConfig(t *testing.T) {
f := newFixture(t, false)
if code, _ := f.do(t, "PUT", "/tenants/acme/language", `{"language":"fr"}`, true); code != http.StatusBadRequest {
t.Fatalf("unregistrierte sprache: status %d, want 400", code)
}
if code, out := f.do(t, "PUT", "/tenants/acme/language", `{"language":"en"}`, true); code != 200 || out["language"] != "en" {
t.Fatalf("%d %v", code, out)
}
if _, out := f.do(t, "GET", "/tenants/acme/language", "", true); out["language"] != "en" {
t.Fatalf("%v", out)
}
}
+1 -1
View File
@@ -10,8 +10,8 @@ import (
"net/http"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/summarize"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/svcauth"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/threadsummarize"
"gitea.perlbach24.de/scripte/nexarch/common/svcauth"
)
type Server struct {
+3 -3
View File
@@ -125,10 +125,10 @@ func wordSet(s string) map[string]bool {
// TypeMetrics fasst die Bewertungen aller Stichproben eines
// Zusammenfassungstyps zusammen (AK1).
type TypeMetrics struct {
Type string
Count int
Type string
Count int
AverageGroundedness float64
FlaggedCount int
FlaggedCount int
}
// AggregateByType berechnet je Zusammenfassungstyp die Kennzahl über alle
-20
View File
@@ -1,20 +0,0 @@
// Package svcauth vergleicht Service-Token in konstanter Zeit (eigene Kopie
// je Modul, siehe workflow/internal/svcauth — Go-Module teilen keine
// internal/-Pakete über Modulgrenzen; Lehre aus RET-14/F-08: Schutz beim
// ersten Entwurf einbauen statt nachzuruesten).
package svcauth
import (
"crypto/sha256"
"crypto/subtle"
)
// Equal vergleicht die SHA-256-Werte beider Eingaben in konstanter Zeit.
// Ein leeres erwartetes Token stimmt nie.
func Equal(got, want string) bool {
if want == "" {
return false
}
g, w := sha256.Sum256([]byte(got)), sha256.Sum256([]byte(want))
return subtle.ConstantTimeCompare(g[:], w[:]) == 1
}
+7 -2
View File
@@ -26,6 +26,8 @@ type Suggestion struct {
type Suggester struct {
Provider provider.Provider
Model string
// Language ist der Anzeigename der Ausgabesprache; leer = keine Vorgabe.
Language string
}
type modelSuggestion struct {
@@ -47,7 +49,7 @@ func (s *Suggester) Suggest(ctx context.Context, existingTags []string, moduleCo
return nil, errors.New("tagsuggest: kein provider konfiguriert")
}
prompt := buildPrompt(existingTags, moduleContext, documentText)
prompt := buildPrompt(existingTags, moduleContext, s.Language, documentText)
resp, err := s.Provider.Chat(ctx, provider.ChatRequest{
Model: s.Model,
Messages: []provider.Message{
@@ -63,7 +65,7 @@ func (s *Suggester) Suggest(ctx context.Context, existingTags []string, moduleCo
return parseAndDedup(existingTags, resp.Message.Content), nil
}
func buildPrompt(existingTags []string, moduleContext, documentText string) string {
func buildPrompt(existingTags []string, moduleContext, language, documentText string) string {
var b strings.Builder
b.WriteString("Schlage passende Schlagworte (Tags) für das folgende Dokument vor.\n")
if moduleContext != "" {
@@ -74,6 +76,9 @@ func buildPrompt(existingTags []string, moduleContext, documentText string) stri
b.WriteString(strings.Join(existingTags, ", "))
b.WriteString("\n")
}
if language != "" {
b.WriteString(fmt.Sprintf("Neue Tags schreibst du in der Sprache %q. Bestehende Tags dürfen auch in einer anderen Sprache vorliegen als das Dokument; ein bedeutungsgleicher bestehender Tag hat immer Vorrang vor einem neuen Tag.\n", language))
}
b.WriteString("Antworte NUR mit einem JSON-Array in diesem Format: ")
b.WriteString(`[{"tag": string, "confidence": number zwischen 0 und 1, "reasoning": string}, ...].`)
b.WriteString(" Schlage höchstens 8 Tags vor, keine Dopplungen.\n\n")
+38 -1
View File
@@ -2,6 +2,7 @@ package tagsuggest
import (
"context"
"strings"
"testing"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/provider"
@@ -27,7 +28,6 @@ func (s *stubProvider) Embed(ctx context.Context, req provider.EmbeddingRequest)
return provider.EmbeddingResponse{}, nil
}
// AK1: Konfidenz wird je Vorschlag mitgeliefert.
func TestSuggest_ReturnsConfidencePerSuggestion(t *testing.T) {
p := &stubProvider{response: `[{"tag":"rechnung","confidence":0.9,"reasoning":"enthaelt rechnungsnummer"},{"tag":"steuer","confidence":0.4,"reasoning":"erwaehnt umsatzsteuer"}]`}
@@ -126,3 +126,40 @@ func TestSuggest_NoProvider_ReturnsError(t *testing.T) {
t.Fatal("erwartet Fehler ohne konfigurierten Provider")
}
}
type capturingProvider struct {
stubProvider
prompt string
}
func (c *capturingProvider) Chat(ctx context.Context, req provider.ChatRequest) (provider.ChatResponse, error) {
c.prompt = req.Messages[len(req.Messages)-1].Content
return c.stubProvider.Chat(ctx, req)
}
// CLS-08: Ausgabesprache wird in den Prompt aufgenommen, bestehende Tags in anderer Sprache behalten Vorrang.
func TestSuggest_LanguageInPromptAndTaxonomyKept(t *testing.T) {
p := &capturingProvider{stubProvider: stubProvider{response: `[{"tag":"rechnung","confidence":0.9}]`}}
s := &Suggester{Provider: p, Model: "test", Language: "Deutsch"}
got, err := s.Suggest(context.Background(), []string{"Rechnung"}, "dms", "Invoice for consulting services")
if err != nil {
t.Fatal(err)
}
if len(got) != 1 || got[0].Tag != "Rechnung" {
t.Fatalf("taxonomie nicht respektiert: %+v", got)
}
if !strings.Contains(p.prompt, `"Deutsch"`) || !strings.Contains(p.prompt, "Vorrang") {
t.Fatalf("sprachvorgabe fehlt im prompt: %s", p.prompt)
}
}
func TestSuggest_NoLanguageNoInstruction(t *testing.T) {
p := &capturingProvider{stubProvider: stubProvider{response: `[]`}}
if _, err := (&Suggester{Provider: p}).Suggest(context.Background(), nil, "", "x"); err != nil {
t.Fatal(err)
}
if strings.Contains(p.prompt, "Sprache") {
t.Fatalf("unerwartete sprachvorgabe: %s", p.prompt)
}
}
@@ -7,7 +7,7 @@ import (
"testing"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/migrate"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/testdb"
"gitea.perlbach24.de/scripte/nexarch/common/testdb"
)
func newStore(t *testing.T) *Store {
-62
View File
@@ -1,62 +0,0 @@
// Package testdb liefert Integrationstests einen Pool auf ein eigenes,
// frisch angelegtes Schema (search_path = Schema, public). Tabellen, die ein
// Test per CREATE TABLE anlegt, entstehen damit nur in diesem Schema; Pakete,
// Laeufe und parallele Checkouts teilen sich keine Tabellen mehr.
package testdb
import (
"context"
"fmt"
"sync/atomic"
"testing"
"time"
"github.com/jackc/pgx/v5/pgxpool"
)
var seq atomic.Int64
// Pool legt ein Schema an und liefert einen darauf ausgerichteten Pool;
// beides wird per t.Cleanup wieder entfernt.
func Pool(t testing.TB, dsn string) *pgxpool.Pool {
t.Helper()
pool, _ := PoolWithSchema(t, dsn, nil)
return pool
}
// PoolWithSchema wie Pool, zusaetzlich mit dem Schemanamen und der
// Moeglichkeit, die Verbindungskonfiguration anzupassen.
func PoolWithSchema(t testing.TB, dsn string, adjust func(*pgxpool.Config)) (*pgxpool.Pool, string) {
t.Helper()
ctx := context.Background()
admin, err := pgxpool.New(ctx, dsn)
if err != nil {
t.Fatalf("testdb: verbindung: %v", err)
}
schema := fmt.Sprintf("connecttest_%d_%d", time.Now().UnixNano(), seq.Add(1))
if _, err := admin.Exec(ctx, "CREATE SCHEMA "+schema); err != nil {
admin.Close()
t.Fatalf("testdb: schema anlegen: %v", err)
}
cfg, err := pgxpool.ParseConfig(dsn)
if err != nil {
admin.Close()
t.Fatalf("testdb: dsn: %v", err)
}
cfg.ConnConfig.RuntimeParams["search_path"] = schema + ",public"
if adjust != nil {
adjust(cfg)
}
pool, err := pgxpool.NewWithConfig(ctx, cfg)
if err != nil {
admin.Close()
t.Fatalf("testdb: pool: %v", err)
}
t.Cleanup(func() {
pool.Close()
_, _ = admin.Exec(context.Background(), "DROP SCHEMA "+schema+" CASCADE")
admin.Close()
})
return pool, schema
}
@@ -30,9 +30,9 @@ type Thread struct {
// Result ist die Zusammenfassung eines Threads.
type Result struct {
Summary string
MessageCount int
LastMessageID string
Summary string
MessageCount int
LastMessageID string
}
// Options steuert die Zusammenfassung.
+97
View File
@@ -0,0 +1,97 @@
package titlesuggest
import (
"context"
"encoding/json"
"errors"
"fmt"
"strings"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/provider"
)
const (
maxTitleRunes = 120
maxSuggestions = 3
)
type Suggestion struct {
Title string `json:"title"`
Confidence float64 `json:"confidence"`
}
type Suggester struct {
Provider provider.Provider
Model string
}
func (s *Suggester) Suggest(ctx context.Context, moduleContext, language, documentText string) ([]Suggestion, error) {
if s.Provider == nil {
return nil, errors.New("titlesuggest: kein provider konfiguriert")
}
resp, err := s.Provider.Chat(ctx, provider.ChatRequest{
Model: s.Model,
Messages: []provider.Message{
{Role: provider.RoleSystem, Content: "Du bist ein Assistent für Dokumenttitel. Antworte ausschließlich mit einem JSON-Array von Objekten, ohne Erklärtext."},
{Role: provider.RoleUser, Content: buildPrompt(moduleContext, language, documentText)},
},
Temperature: 0,
})
if err != nil {
return nil, fmt.Errorf("titlesuggest: modellaufruf: %w", err)
}
return parse(resp.Message.Content), nil
}
func buildPrompt(moduleContext, language, documentText string) string {
var b strings.Builder
b.WriteString("Schlage prägnante Titel für das folgende Dokument vor.\n")
if moduleContext != "" {
fmt.Fprintf(&b, "Kontext: das Dokument stammt aus dem Modul %q.\n", moduleContext)
}
if language != "" {
fmt.Fprintf(&b, "Schreibe die Titel in der Sprache %q.\n", language)
}
fmt.Fprintf(&b, "Antworte NUR mit einem JSON-Array: [{\"title\": string, \"confidence\": number zwischen 0 und 1}, ...]. Höchstens %d Titel, je höchstens %d Zeichen.\n\nDokument:\n", maxSuggestions, maxTitleRunes)
b.WriteString(documentText)
return b.String()
}
func parse(raw string) []Suggestion {
start, end := strings.IndexByte(raw, '['), strings.LastIndexByte(raw, ']')
if start < 0 || end < start {
return nil
}
var in []Suggestion
if err := json.Unmarshal([]byte(raw[start:end+1]), &in); err != nil {
return nil
}
seen := make(map[string]bool)
var out []Suggestion
for _, s := range in {
title := strings.Join(strings.Fields(s.Title), " ")
if r := []rune(title); len(r) > maxTitleRunes {
title = strings.TrimSpace(string(r[:maxTitleRunes]))
}
key := strings.ToLower(title)
if title == "" || seen[key] {
continue
}
seen[key] = true
out = append(out, Suggestion{Title: title, Confidence: clamp(s.Confidence)})
if len(out) == maxSuggestions {
break
}
}
return out
}
func clamp(v float64) float64 {
if v < 0 {
return 0
}
if v > 1 {
return 1
}
return v
}
@@ -0,0 +1,59 @@
package titlesuggest
import (
"context"
"os"
"strings"
"testing"
"time"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/provider/ollama"
)
func TestSuggest_RealInference_Accuracy(t *testing.T) {
base, model := os.Getenv("TEST_OLLAMA_BASE_URL"), os.Getenv("TEST_OLLAMA_MODEL")
if base == "" || model == "" {
t.Skip("TEST_OLLAMA_BASE_URL/TEST_OLLAMA_MODEL nicht gesetzt, Realtest uebersprungen")
}
s := &Suggester{Provider: ollama.New(base), Model: model}
cases := []struct {
name, text string
want []string
}{
{"rechnung", "Rechnung Nr. 2024-0451\nMusterfirma GmbH\nBetrag: 1.297,00 EUR zzgl. 19% USt\nLeistung: Beratung September 2026", []string{"rechnung", "2024-0451"}},
{"vertrag", "Dienstleistungsvertrag zwischen Musterfirma GmbH und Beispiel AG. Laufzeit 24 Monate, Kuendigungsfrist 3 Monate zum Quartalsende.", []string{"vertrag"}},
{"lieferschein", "Lieferschein Nr. 88213\n20x Aktenordner, 5x Tonerkartuschen\nLieferung am 03.10.2026 an Musterstrasse 1", []string{"lieferschein", "88213"}},
{"mahnung", "Zahlungserinnerung: Die Rechnung 2024-0451 vom 15.11.2026 ist noch offen. Bitte zahlen Sie bis zum 30.11.2026.", []string{"mahnung", "erinnerung", "zahlung"}},
{"invoice-en", "Invoice No. 7731, Sample Corp. Amount due: 540.00 EUR. Payment terms: 14 days net.", []string{"invoice", "7731", "rechnung"}},
}
hits := 0
for _, c := range cases {
ctx, cancel := context.WithTimeout(context.Background(), 90*time.Second)
got, err := s.Suggest(ctx, "dms", "Deutsch", c.text)
cancel()
if err != nil {
t.Fatalf("%s: %v", c.name, err)
}
hit := false
for _, g := range got {
if g.Confidence < 0 || g.Confidence > 1 {
t.Errorf("%s: konfidenz ausserhalb [0,1]: %v", c.name, g.Confidence)
}
for _, w := range c.want {
if strings.Contains(strings.ToLower(g.Title), w) {
hit = true
}
}
}
t.Logf("%s: hit=%v vorschlaege=%+v", c.name, hit, got)
if hit {
hits++
}
}
t.Logf("Trefferquote: %d/%d", hits, len(cases))
if hits < 3 {
t.Errorf("trefferquote %d/%d unter 3/5", hits, len(cases))
}
}
@@ -0,0 +1,61 @@
package titlesuggest
import (
"context"
"errors"
"strings"
"testing"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/provider"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/provider/mock"
)
func newSuggester(resp string) *Suggester {
return &Suggester{
Provider: &mock.Provider{ChatResponse: provider.ChatResponse{Message: provider.Message{Role: provider.RoleAssistant, Content: resp}}},
Model: "test",
}
}
func TestSuggest_ReturnsConfidencePerTitle(t *testing.T) {
got, err := newSuggester(`[{"title":"Rechnung 2026-001","confidence":0.9},{"title":"Rechnung Muster GmbH","confidence":2}]`).
Suggest(context.Background(), "dms", "de", "Rechnung")
if err != nil {
t.Fatal(err)
}
if len(got) != 2 || got[0].Confidence != 0.9 || got[1].Confidence != 1 {
t.Fatalf("unerwartet: %+v", got)
}
}
func TestSuggest_DedupTrimAndLimit(t *testing.T) {
long := strings.Repeat("a", 300)
got, err := newSuggester(`[{"title":" Vertrag A ","confidence":0.5},{"title":"vertrag a","confidence":0.4},{"title":"","confidence":1},{"title":"`+long+`","confidence":0.3},{"title":"B","confidence":0.2},{"title":"C","confidence":0.1}]`).
Suggest(context.Background(), "", "", "x")
if err != nil {
t.Fatal(err)
}
if len(got) != 3 || got[0].Title != "Vertrag A" {
t.Fatalf("unerwartet: %+v", got)
}
if n := len([]rune(got[1].Title)); n != maxTitleRunes {
t.Fatalf("titel nicht gekuerzt: %d", n)
}
}
func TestSuggest_GarbageYieldsEmpty(t *testing.T) {
got, err := newSuggester("kein json").Suggest(context.Background(), "", "", "x")
if err != nil || len(got) != 0 {
t.Fatalf("got=%v err=%v", got, err)
}
}
func TestSuggest_ProviderErrorAndNil(t *testing.T) {
s := &Suggester{Provider: &mock.Provider{Err: errors.New("down")}}
if _, err := s.Suggest(context.Background(), "", "", "x"); err == nil {
t.Fatal("fehler erwartet")
}
if _, err := (&Suggester{}).Suggest(context.Background(), "", "", "x"); err == nil {
t.Fatal("fehler erwartet")
}
}
+1 -1
View File
@@ -11,8 +11,8 @@ import (
"github.com/jackc/pgx/v5/pgxpool"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/migrate"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/testdb"
"gitea.perlbach24.de/scripte/nexarch/ai/internal/vectorindex"
"gitea.perlbach24.de/scripte/nexarch/common/testdb"
)
func newPool(t *testing.T) *pgxpool.Pool {
@@ -0,0 +1 @@
DROP TABLE ai_origin_markers;
@@ -0,0 +1,5 @@
CREATE TABLE ai_origin_markers (
tenant_slug TEXT PRIMARY KEY,
marker TEXT NOT NULL CHECK (marker <> ''),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
@@ -0,0 +1 @@
DROP TABLE ai_tenant_languages;
@@ -0,0 +1,5 @@
CREATE TABLE ai_tenant_languages (
tenant_slug TEXT PRIMARY KEY,
language TEXT NOT NULL CHECK (language ~ '^[a-z]{2,3}$'),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
+1 -1
View File
@@ -12,9 +12,9 @@ import (
"github.com/jackc/pgx/v5/pgxpool"
"gitea.perlbach24.de/scripte/nexarch/archive/internal/complianceapi"
"gitea.perlbach24.de/scripte/nexarch/archive/internal/httpharden"
"gitea.perlbach24.de/scripte/nexarch/archive/internal/rbacclient"
"gitea.perlbach24.de/scripte/nexarch/archive/internal/retentionapi"
"gitea.perlbach24.de/scripte/nexarch/common/httpharden"
)
func main() {
+1 -1
View File
@@ -10,10 +10,10 @@ import (
"github.com/jackc/pgx/v5/pgxpool"
"gitea.perlbach24.de/scripte/nexarch/archive/internal/httpharden"
"gitea.perlbach24.de/scripte/nexarch/archive/internal/kennzahlenapi"
"gitea.perlbach24.de/scripte/nexarch/archive/internal/rbacclient"
"gitea.perlbach24.de/scripte/nexarch/archive/internal/retentionapi"
"gitea.perlbach24.de/scripte/nexarch/common/httpharden"
)
func main() {
+2 -2
View File
@@ -13,10 +13,10 @@ import (
"github.com/jackc/pgx/v5/pgxpool"
"gitea.perlbach24.de/scripte/nexarch/archive/internal/httpharden"
"gitea.perlbach24.de/scripte/nexarch/archive/internal/legalholdapi"
"gitea.perlbach24.de/scripte/nexarch/archive/internal/moduleadapter"
"gitea.perlbach24.de/scripte/nexarch/archive/internal/tenantregistryclient"
"gitea.perlbach24.de/scripte/nexarch/common/httpharden"
"gitea.perlbach24.de/scripte/nexarch/common/tenantregistryclient"
)
func main() {
+1 -1
View File
@@ -10,8 +10,8 @@ import (
"net/http"
"os"
"gitea.perlbach24.de/scripte/nexarch/archive/internal/httpharden"
"gitea.perlbach24.de/scripte/nexarch/archive/internal/restoretest"
"gitea.perlbach24.de/scripte/nexarch/common/httpharden"
)
func main() {
+1 -1
View File
@@ -13,9 +13,9 @@ import (
"github.com/jackc/pgx/v5/pgxpool"
"gitea.perlbach24.de/scripte/nexarch/archive/internal/httpharden"
"gitea.perlbach24.de/scripte/nexarch/archive/internal/rbacclient"
"gitea.perlbach24.de/scripte/nexarch/archive/internal/retentionapi"
"gitea.perlbach24.de/scripte/nexarch/common/httpharden"
)
func main() {
+1 -1
View File
@@ -14,8 +14,8 @@ import (
"github.com/jackc/pgx/v5/pgxpool"
"gitea.perlbach24.de/scripte/nexarch/archive/internal/httpharden"
"gitea.perlbach24.de/scripte/nexarch/archive/internal/scrub"
"gitea.perlbach24.de/scripte/nexarch/common/httpharden"
)
func main() {
Loaded 100 of 563 files, more files were not shown because too many files have changed in this diff. Show more