Kansho/docs/architecture/technical/privacy_gateway.md
Lars cc43fc6409
Some checks failed
Test Suite / pytest-backend (push) Waiting to run
Test Suite / smoke-dev (push) Waiting to run
Test Suite / frontend-build (push) Waiting to run
Deploy Development / deploy (push) Has been cancelled
Offer kinship names in learning review when Detect skips them.
Frau/Sohn mentions in the user line must still reach the popup if OpenRouter only tags the food reading or misses the person.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-09 08:47:44 +02:00

31 KiB
Raw Blame History

title version status date product_family document_role parent_document
Kanshō Privacy Gateway und AI-Egress 0.1 Arbeitsstand 2026-08-19 Jinkendo Technical Chapter / Privacy Gateway / Egress technische_zielarchitektur.md

Kanshō Privacy Gateway und AI-Egress

Technische Abbildung der fachlichen Guardrails. Kanonisches Fachhome: ../functional/guardrails.md (inkl. Root Cause §2, Datenklassen §5, Gateway §6, Entscheidungsstand §22). Dieses Kapitel dupliziert die Herleitung nicht.

Rang: Datenschutz und Guardrails sind im Fachkonzept grundlegend und entschieden. Sie stehen nicht auf derselben Ebene wie unfertige Dialog- oder MVP-Schnitte. Offene Punkte betreffen Verfahren und rechtliche Ausprägung (Entity Detection, Encryption at rest, DSFA), nicht die Invariante selbst.

1. Invariante

Identität bleibt lokal. Externe Intelligenz erhält nur den für die Aufgabe notwendigen, minimierten und soweit sinnvoll pseudonymisierten Kontext.

Herkunft: guardrails.md §2.4; querschnittlich in der fachlichen Zielarchitektur. Providerunabhängig: OpenRouter ist Kandidat, kein Ersatz für das lokale Gateway. OpenRouter-eigene Guardrail-Features ergänzen höchstens, sie ersetzen die Kanshō-Schicht nicht.

Pseudonymisierung ist keine Anonymisierung. Kanshō behauptet keine vollständige Anonymität nach außen.

Persönlicher Kontext wird nie direkt aus UI, Dialog-Router, Prompt-Engine oder Admin-Preview an OpenRouter, Provider oder Tools gesendet. Die Engine darf den Provider nur über den Gateway-Callback erreichen (platform_extensibility.md).

1.1 Guardrail-Vorrang

Fachlich §19: Guardrails haben Vorrang vor Modellqualität, Kosten, Latenz, Komfort und automatischem Provider-Fallback. Eine Anfrage wird abgelehnt oder degradiert, wenn die Policy nicht erfüllbar ist. Administrierbare Prompts, Workflows oder Feature-Flags dürfen das Gateway nicht abschalten.

2. Trust Zones

Zone Inhalt Egress
Local Trusted Zone Identity Mapping, Klarnamen, Secrets, volle Primärquellen, Obsidian-Pfade kein Roh-Egress
External AI Zone External Model Context (Klasse B) nur nach Policy-Check, ZDR
Tool-/Plugin-Zone Suche, Plugins, Speech eigene Policy, nicht implizit mit LLM-Freigabe

3. Datenklassen

  • A Local Only: Mapping, Secrets, vollständige Identität, interne IDs soweit identifizierend. Verlassen die Trusted Zone nicht.
  • B Pseudonymized AI Context: Normalfall persönlicher Dialoge. Stabile, nicht sprechende Platzhalter ([[SELF]], [[PERSON:PARTNER]]). Minimierung zusätzlich zur Maskierung.
  • C Low-Identity: generische, nicht-personalisierte Inhalte; schwächere Transformation zulässig, eigene Policy bleibt nötig.

Quasi-Identifikatoren (Beruf+Ort+Familie) sind fachlich erkannt; die Bewertungsmethode ist offen, der Gateway-Auftrag „nicht nur Klarnamen“ gilt trotzdem.

4. Pflichtpipeline

Jeder persönliche Modellaufruf durchläuft lokal:

  1. Context Minimization
  2. Entity Detection
  3. Pseudonymization
  4. Policy Check
  5. Provider/Endpoint Eligibility (ZDR, kein Training, kein Prompt-Logging)
  6. Egress Validation
  7. Response Validation
  8. Local Rehydration / Demasking
  9. Audit Metadata (ohne volle Prompts)
  10. Fail Closed

Fail Closed: kein stillschweigender Fallback auf einen weniger geschützten Provider.

LLM-Egress und Tool-/Web-/Transkriptions-Egress sind getrennte Policies. Ein für ZDR-LLM freigegebener Kontext darf nicht automatisch an Web Search oder Speech-Cloud.

5. Internal vs. External Context

Der Context Builder darf intern mit Klaridentität arbeiten. Vor dem Socket nach außen existiert eine zweite Repräsentation ohne Mapping-Tabelle.

Demasking ausschließlich in der Trusted Zone, nach Validierung (Platzhalter erhalten, keine erratenen Klarnamen, keine Policy-Verstöße).

6. Audit

Speichern: Request-ID, Zeit, Policy, Provider-/Modellkennung, ZDR-/Region-Status, Typ maskierter Entitäten, Prüf- und Fallback-Status.

Nicht speichern: volle Prompts, volle Antworten, echte Namen, Mapping.

Admin-Sichtbarkeit der Mapping-Tabelle nur für besonders berechtigte Rollen (admin_diagnostics.md).

7. Beziehung zu anderen Kapiteln

  • Resurfacing darf keinen breiteren Egress rechtfertigen als der aktuelle Task braucht.
  • Re-Grounding läuft lokal und erzeugt danach erneut einen minimierten External Context.
  • Voice/Transkription: eigener Egress-Typ (voice_and_media.md).
  • Lived Experience / Re-Grounding: volle Primärquellen bleiben lokal; externes Modell bekommt erneut nur Klasse B.
  • Prompt-Engine: Preview ohne LLM ist erlaubt; Debug-Ausgaben an den Admin ohne Klasse-A-Klartext und ohne Mapping.

8. Was Phase H noch offen lässt

Interview H1 (Encryption at rest, vollständiges Löschen, Portabilität, DSFA bei späterem Mehrbenutzerbetrieb) ergänzt diese Invariante, ersetzt sie nicht. Privacy Profiles (Strict / Balanced / Low-Identity) sind fachlich perspektivisch; Default muss die Invariante erfüllen.

9. Entscheidungsstand

Thema Stand Status
Privacy Gateway vor persönlichem Egress zwingend, nicht abschaltbar entschieden
Guardrails vor Qualität/Kosten/Latenz ja entschieden (fachlich)
Providerunabhängige Guardrails ja entschieden (fachlich)
ZDR / kein Training / kein Prompt-Log persönliche Kontexte entschieden bzw. bevorzugte verbindliche Policy
Fail Closed ja bevorzugte Richtung
Keine Anonymitätsbehauptung Pseudonymisierung entschieden (fachlich)
OpenRouter Kandidat, nicht Gateway-Ersatz entschieden
EU-Routing bevorzugt offen in Ausprägung
Encryption at rest, Delete, DSFA Phase H offen
Implementierung Entity Detection Semantische Request-Detection; Detect-Ausgabe nicht auto-persistiert; Qualität des Modells offen entschieden im Verfahren, Modellqualität unbestätigt

9.1 Implementierungsstand (MVP-Egress)

Status: Code vorhanden. Zwei getrennt konfigurierbare Provider. Fit-Gap und Guardrail-Abstand (Detect-Testphase, Security Layer): ../functional/mvp_stand_und_abgleich.md.

backend/privacy_gateway.py ist die einzige Stelle für den generativen Egress. Überholt (2026-08-26): Muster-Fallback, known_labels, automatisches Speichern jedes Detect-Treffers und stilles Abschneiden nach DETECT_MAX_CHARS. Aktuell: §9.5.

Intern in der Testspur ist keine dritte Anfrage, sondern die lokale Klartext-Vorlage desselben Dialogzugs. Detect und Dialogzug bleiben getrennte Calls.

Pronomen werden nicht als eigene Detect-Treffer gespeichert. Nach der Namensmaskierung bindet eine lokale Schicht im Dialogzug in user:-Zeilen er/sie/ihm/ihr an das letzte kompatible Person-Token, damit das Sprachmodell dieselbe Person sieht. Beim Journalentwurf bleiben diese Pronomen ungebunden, sonst macht die Demaskierung aus jedem Bezug den Klarnamen. Anrede-ihr und pluralisches sie bleiben ungebunden. Dieselbe Person behält ein Token; PERSON:KURZ ist kein Mapping. Ein Personenname, der auch eine Sache ist, wird bei Verwandtschaftswort maskiert und bei Verben wie essen/kochen/kaufen davor oder danach als Sache stehen gelassen (Sushi essen, esse Sushi).

Zwei Provider, getrennt konfigurierbar (providers.py, Admin Schnittstellen):

Drei Schichten, zwei technische Endpunkte (provider_settings-Rollen generate / detect sind Implementierungsnamen):

  • Maskierung (Rolle detect): vollständige semantische Detection des persönlichen Generate-Egress. Keine Operation, kein Impuls, kein Journaltext. Testphase: OpenRouter-Detect darf Klartext sehen. Produktivziel: lokales Modell. Kein Pattern-Fallback. Wiederverwendbar für spätere Dialogtypen.
  • Sprachmodell (Rolle generate): maskierter Kontext. Dient zwei fachlichen Calls: Dialogzug (Operation + Impuls) und Journalentwurf (explizit). Das größere Modell gehört hierhin, nicht an die Maskierung.

URL, Modell und Policy liegen in provider_settings (Seed config/providers.seed.json, Admin GET/PUT /api/admin/providers). Keys nur in backend/.env, nie in der DB und nie in der API-Antwort. Env überschreibt Admin-Werte, wenn gesetzt.

Lokale URLs (localhost / 127.0.0.1) brauchen keine ZDR-Flags. Die Mapping-Tabelle bleibt Klasse A und lokal. Detect-Ausgaben erzeugen keine aktiven Registry-Einträge; siehe §9.5.

Klasse A verlässt die Zone nicht. Mapping wird nicht mitgeschickt. Kein stiller Fallback auf einen unsicheren Generate-Provider.

Prompts für Dialog, Journal-Rekonstruktion, Journal-Narration, Detection und Profile-Review liegen in der DB (mvp.dialogue_turn, mvp.journal_reconstruct, mvp.journal_generate, mvp.entity_detect, mvp.profile_review), nicht im Anwendungscode.

mvp.dialogue_turn bleibt ein generativer Call (Foundation: ein Hauptcall pro Turn). Ton, Tiefe und Erzählmodus kommen lokal als register_hint plus Writing-Profile-Hinweis in denselben Prompt, nicht als zweites Modell. JSON {operation, impulse}. Persistiert wird nur impulse.

Admin-Testspur (trace) zeigt intern gerenderten Prompt und maskierten Egress. Das ist ein bewusster Test-Einblick, nicht der fachliche Default aus §2 (Diagnose ohne volle Prompts). Mapping-Tabelle bleibt ausgeschlossen.

9.2 Offene Implementierungspunkte (Security Layer)

Status: bewusst offen. Der MVP-Pfad ist durchgängig, der fachliche Security Layer aus ../functional/guardrails.md ist nicht vollständig implementiert. Für Testdaten akzeptabel; nicht als abgeschlossen führen.

Noch offen im Code:

  1. Inhaltliche Context Minimization. Es geht der zusammengebaute Dialog, nur durch eine Zeichenkappe begrenzt. Minimum-sufficient-context nach Aufgabe fehlt.
  2. Robuste Entity Detection. Semantische Request-Detection mit Chunking ist gebaut (§9.5). Testphase: externes Detect-Modell über OpenRouter. Ziel: lokales Ollama. Fake-Provider-Tests beweisen nicht die semantische Modellqualität. Quasi-Identifikatoren (Beruf + Ort + Familie) bleiben unerkannt.
  3. Response Validation. Nach dem Generate: Platzhalterintegrität, lokale Rehydrierung und Kennzeichnung. Aktiver Klartext im Reply wird dem Request-Token zugeordnet (active_cleartext_normalized) und danach demaskiert. Das verhindert keinen bereits erfolgten Egress und startet keinen zweiten Generate-Call. Journalspezifische Faktentreue entscheidet der Journal-Adapter. Dialog-Egress maskiert den gesamten gerenderten Prompt. Der Dialogzug darf lokal zurückfallen; ein verworfener Journaltext wird nicht als Entwurf gespeichert. Keine Quasi-Identifikatoren, keine inhaltliche Minimierung.
  4. Audit ohne Prompt-Inhalt. Es gibt keine persistente Audit-Spur (Request-ID, Policy, Provider, ZDR-Status, maskierte Entitätstypen) ohne volle Prompts oder Mapping.
  5. Mapping-Härtung. identity_mappings ist lokal, aber unverschlüsselt. Admin-Review unter /admin/identities existiert. Keine Verschlüsselung at rest. Legacy-Zeilen sind legacy_review_required, nicht automatisch bestätigt.
  6. Detect-Klartext in der Testphase. Der Detect-Provider darf Klartext sehen. Bei externem Detect (OpenRouter) ist das ein bewusster Übergang, nicht der Zielpfad. Produktiv nur lokales Modell.
  7. Weitere Egress-Klassen. Tool-, Web- und Speech-Egress bleiben getrennt und ungebaut.
  8. Löschen, Export, DSFA. Journal-Entries haben Soft Delete, Papierkorb und explizites Purge. Kein vollständiges Account-Löschen, keine Portabilität als öffentliches Produkt, keine Datenschutz-Folgenabschätzung.

Diese Punkte dürfen nicht stillschweigend als erledigt gelten, nur weil Generate bereits maskiert.

9.3 Journal-Budget und Context Compression (2026-08-25)

Additiv zu §9.1. Fachliche Trennung Inhalt/Stil: ../functional/writing_profile_and_journaling.md §16. Technische Umsetzung: mvp_implementation.md §16.

Persönlicher Journal-Egress bleibt ausschließlich über dieses Gateway. Der normale Journal-Lauf hat genau einen generativen Zweck: journal_generate. Stufe 1 ist lokal (local_source_artifact) und erzeugt kein Providerbudget. Kein stiller Fallback auf ein anderes Modell. OpenRouter-Context-Compression ist für Journal-Calls ausdrücklich deaktiviert (plugins.id=context-compression, enabled=false). Diese Plugin-Felder gehen nicht an lokale oder fremde OpenAI-kompatible Endpunkte.

Journal-Prompts werden nicht mehr durch MAX_EGRESS_CHARS still in der Mitte abgeschnitten. Passt Artefakt, Writing Profile und optionaler Bestand nicht ins Stufe-2-Budget, bricht Kanshō mit prompt_budget_exceeded ab. Stufe 2 erhält den vollständigen lokal rehydrierten Nutzerinhalt als VerifiedArtifact (provenance_verification.md); bei Übergröße ebenfalls Abbruch, kein stilles Kürzen.

Additiv 2026-08-29: Der von der Stilanwendungsvariante freigegebene Stilkontext (Writing-Profile-Core, Facet, Traits, Style Examples) durchläuft weiterhin vollständig das Privacy Gateway. Ohne persönliches Profil sendet keinen Profilinhalt und keine Style Examples. Nicht freigegebene Stilquellen fehlen im Egress, sie werden nicht nachträglich angehängt. Die Gateway-Policy selbst ändert sich nicht.

Usage-Metadaten (Tokens, Kosten, Fenster, Reserve, Marge, Compression-Status) liegen in der Admin-Testspur trace.budget bzw. trace.stages. Journal-Generate setzt lokale Stufe 1 und den Gateway-Call der Stufe 2 zusammen; run_log sammelt Detect, Pre-Egress, den Generate-Aufruf, Antwortnormalisierung und Provenienzentscheidung ohne Promptkörper. Es gibt keine globale debug_history und keinen globalen Promptspeicher. Vollständige Prompts und Antworten höchstens im direkten Admin-Response des aktuellen Requests. Mapping bleibt ausgeschlossen.

Additiv 2026-08-28 (opt-in lokale Persistenz): Admin kann unter /admin/debug eine lokale Debug-Persistenz einschalten. Default bleibt aus. Dann speichert Kanshō die Testspur jedes Admin-Schritts in debug_runs, zugeordnet zu Space / Tag / Gespräch. Mapping-Tabelle, Labels und Secrets bleiben ausgeschlossen. Kein In-Memory-Promptspeicher, kein Egress des Debug-Archivs, kein Bypass. Die Live-Anzeige unter dem Dialog entfällt; der Dialog bietet den Gesprächs-Download, solange der Schalter an ist. Der Journal-Entwurf zeigt die Testspur weiter und erlaubt den JSON-Download des Generate-Laufs. Export kansho.debug_export v2 (JSON-Baum plus chronologische runs, Markdown) nur für das eigene Admin-Profil. Nicht-Admin-Aktionen werden nicht geschrieben. Abschalten stoppt neue Writes; vorhandene Zeilen bleiben bis zum expliziten Löschen. Das ist keine Produktions-Audit-Spur und kein Ersatz für §9.2 Punkt 4.

9.4 Request-scoped Maskierungsmanifest (2026-08-26)

Intent-neutral, im Gateway. Journalspezifische Provenienz- und Ablehnungspolicy bleibt im Journal-Adapter.

Ein Mapping ist nicht allein deshalb aktiv, weil es in der Profiltabelle existiert. Aktiv ist nur, was im aktuellen gerenderten Prompt nach derselben Identitätsregel wie die Maskierung tatsächlich ersetzt wurde. Homonyme, die absichtlich unmaskiert bleiben, werden dadurch nicht aktiv.

Das Manifest ist request-lokal: maskierter Egress-Text, aktive Tokens und Entitätstypen, optionale lokale Vorkommenszahl. local_label bleibt auf dem Request-Objekt und geht nicht in Logs, Compact-Diagnose oder persistente Spuren. Keine globale mutable Speicherung; parallele Requests und Profile bleiben getrennt. mask_for_egress() bleibt als String-Hülle.

Vor dem Provider: Pre-Egress-Validierung. Für bestätigte Registry-Einträge und labelbasierte Safety-Net-Treffer darf kein als Identität klassifiziertes Klartextvorkommen im Egress bleiben. Request-lokale Detect-Spans maskieren nur die erkannten Offsets; derselbe Wortlaut darf an anderer Stelle Allgemeinbegriff bleiben. Fehlschlag: egress_validation_failed, Provider wird nicht aufgerufen.

Nach dem Provider: Response-Verarbeitung ist Inhaltsintegrität, nicht nachträglicher Egress-Stopp. Aktiver Klartext, der exakt zu einem request-scoped Mapping gehört, wird lokal dem Token zugeordnet (active_cleartext_normalized) — das ist die interne Prüffassung. Die sichtbare und gespeicherte Fassung entsteht aus der ursprünglichen Modellantwort; in ihr werden nur tatsächlich ausgegebene aktive Platzhalter rehydriert. Vom Modell bereits im Klartext ausgegebene, quellbelegte Begriffe behalten ihre Oberflächenform (Groß-/Kleinschreibung, Artikel, Numerus, Kasus, Satzstellung, Zeichensetzung). Unbestätigte Detect-Treffer gelten nicht automatisch als bewiesene Klartextidentität. Es gibt keinen automatischen zweiten Generate-Call. Compact-Diagnose: verfügbare vs. aktive Mappings, maskierte Vorkommen, Tokens/Typen ohne Labels, Pre-Egress, response_normalization, generate_calls, model_text_accepted, Laufzeit und Tokenverbrauch des tatsächlich erfolgten Aufrufs, prompt_slug, prompt_revision, generate_ms. Demaskierung nur mit den aktiven Tokens dieses Manifests; unbekannte oder generische Platzhalter führen keinen Klarname ein. Ein Personenname als Subjekt („Clarissa kaufte …“) bleibt Identität; nur Objekt- und Infinitivkonstruktionen wie „Sushi essen“ gelten als Homonym.

Öffentliche, intent-neutrale Identitätsprüfung: canonical_token, identity_label_pattern, is_identity_mention, identity_occurrence_count. Journalspezifische Provenienz (unbelegte heutige Fakten in Titel und Textkörper) bleibt im Journal-Adapter. Ein nicht lokal lösbarer Journalfehler speichert keinen journal_draft und liefert journal_generation_not_accepted.

9.4.1 Sicherheitsgrenze (2026-08-27)

Privacy fail-closed vor dem externen Socket. Faktentreue, Provenienz und Antwortintegrität nach der Modellantwort. Ein Verwerfen der Antwort macht einen bereits erfolgten Egress nicht ungeschehen. Detect-Ausgaben bleiben untrusted und request-scoped: sie dürfen ausgehend konservativ maskieren, erzeugen keine bestätigte Registry und überspringen keine spätere Detection.

Additiv 2026-08-27: Bestätigte Identitäten mit Alias bleiben tokenbasiert. Wird eine Schreibweise eines Tokens im Request maskiert, bleiben alle bekannten Schreibweisen dieses Tokens (kanonisch und Aliase) im request-scoped Manifest. Die lokale Demaskierung auf die kanonische Form ist deshalb kein unattested_identity, sofern irgendeine Schreibweise desselben Tokens in den heutigen Quellen belegt ist. Compact-Diagnose enthält weiterhin keine Labels.

9.5 Semantische Request-Detection und bestätigte Registry (2026-08-26)

Intent-neutral, im Gateway. Journalbegriffe gehören nicht in diese Schicht.

Überholt: wortlistenbasierter Detect-Prompt, known_labels als Ausnahme von der Prüfung, Pattern-Heuristiken (mit/bei/von plus Großschreibung) als Ersatz für semantische Detection, stilles Abschneiden nach DETECT_MAX_CHARS, Pattern-only-Freigabe des Generate-Calls, automatisches Speichern jedes Detect-Treffers als aktive Identität.

Verfahren

Jeder persönliche Generate-Egress wird vollständig semantisch untersucht, bevor der Generate-Provider aufgerufen wird. Der geprüfte Klartext ist derselbe gerenderte Prompt, der anschließend maskiert versendet wird. Bekannte Labels werden nicht ausgenommen. Ergebnisse gelten zuerst nur für diesen Request.

Überschreitet der Egress das Detect-Fenster, wird lokal in überlappende Chunks geteilt. Jeder Chunk muss syntaktisch gültig und lokal gegen den Chunktext prüfbar sein. Erst nach erfolgreicher Prüfung aller Chunks wird Generate freigegeben. Fehlschlag, Abbruch, unvollständiger Output oder finish_reason=length sind fail-closed: kein Generate, kein Pattern-Fallback, keine Weiterarbeit mit veralteten Mappings.

Detect-Schema

Untrusted Input:

{"entities":[{"start":42,"end":47,"text":"Anna","entity_type":"PERSON"}]}

Erlaubte Typen: PERSON, PLACE, ORG, PROJECT. PROJECT ist additiv entschieden für benannte private Vorhaben und Produktbezeichnungen, nicht für Allerweltsgegenstände. Zusätzliche Felder und Tokens werden abgelehnt. Falsche Offsets werden lokal am exakten Wortlaut im Chunk verankert, sofern das gemeldete text dort vorkommt. Überlappende Spans: längste Spanne, dann links, dann Typreihenfolge PERSON > PROJECT > ORG > PLACE. Tokens vergibt nur die lokale Schicht, request-scoped.

Additiv 2026-08-29 (Detect-Vertrag, ein vollständiger Neuversuch): Eine formal ungültige Detector-Antwort (unbekannter entity_type, Extra-/Fehlfelder, ungültiges JSON, nicht verankerbarer Text, Abbruch/finish_reason=length, unvollständige Chunk-Abdeckung) verwirft den gesamten bisherigen request-lokalen Pass. Es folgt genau ein neuer vollständiger Pass über alle Chunks mit einer allgemeinen Schema-Korrekturanweisung, ohne textspezifische Namen oder Fehlertypen. Misslingt auch der zweite Pass: fail-closed, kein Generate, kein Journalentwurf. Netzwerk- und Timeout-Fehler (detect_chunk_failed) werden nicht durch denselben Vertrag-Retry verdoppelt. Ungültige Entitäten werden nicht still ignoriert, nicht auf erlaubte Typen umgedeutet und nicht durch Pattern-Fallback ersetzt. Compact-Fehlerdiagnose: detect_passes, Versuche mit Chunk-/Aufruf-/Coverage-Zahlen, aggregierte Detect-Tokens/Kosten/Dauer, detect_partial_discarded, contract_violation, bei unbekanntem Typ den gelieferten entity_type ohne Klartextspan, generate_called: false. Unbekannte Providerkosten bleiben detect_cost_unknown. Diagnoseobjekte sind azyklisch (trace.budget ist ein Snapshot, kein Live-diagnostics). Persistenzfehler ersetzen den ursprünglichen EngineError nicht.

Additiv 2026-09-08 (Lebensmittel-Homonym, kein Vertragsbruch): Explizit nicht-identifizierende Detect-Typen (FOOD, DISH, MEAL, CUISINE, FOODSTUFF, INGREDIENT) sind keine Schemaverletzung. Sie werden nicht auf einen erlaubten Typ umgedeutet und nicht maskiert, sofern die lokale Homonymregel denselben Wortlaut als Sache behandelt (Sushi essen, aß Sushi). Steht dieselbe Meldung in einem Personenkontext (Frau Sushi, Sushi kam), gilt lokal die bestehende Identitätsregel und der Span wird als PERSON behalten. Andere unbekannte Typen (HUMAN, NAME, WIFE, …) bleiben fail-closed. Extrafelder, Tokens, unbrauchbare Offsets und unvollständige Chunks bleiben unverändert fail-closed. Compact-Diagnose darf omitted_non_identity_types ohne Klartextspan enthalten.

Schemaverletzungen, Abbruch und unvollständige Chunks bleiben fail-closed. Im Dialogzug erzeugt ein solcher Fail-closed lokal einen Halte-Impuls, ohne den Generate-Provider zu rufen. Der Nutzer sieht keine interne Substring-Diagnose.

RequestDetectionManifest vs. ConfirmedIdentityRegistry

  • RequestDetectionManifest: erkannte Spans, beobachtete Schreibweise, Typ, lokal vergebener Platzhalter. Keine globale Speicherung. Steuert Maskierung, Response Validation und Demaskierung dieses Requests.
  • ConfirmedIdentityRegistry: nur ausdrücklich bestätigte oder lokal autoritative Einträge. Typ, kanonische Schreibweise, optionale bestätigte Aliase, Status, Herkunft, Zeitstempel. Detect-Ausgabe erzeugt niemals automatisch einen aktiven Eintrag.
  • Review-Proposals: optional, unbestätigt, nie maskierend, nie detection-skip, nicht in externe Prompts oder Compact-Traces.

Nach der semantischen Detection prüft die bestätigte Registry den vollen Egress als Sicherheitsnetz. Legacy-Zeilen (legacy_review_required) zählen nicht als bestätigt.

Demaskierung

Unbestätigte Request-Treffer werden in der beobachteten Form demaskiert. Bestätigte Identitäten und bestätigte Aliase werden auf die kanonische Schreibweise demaskiert, wenn das Modell den Platzhalter ausgegeben hat. Klartext, den das Modell selbst geschrieben hat, wird nicht über die Prüfnormalisierung auf die gespeicherte Manifestform zurückgebogen. Unbekannte Platzhalter materialisieren keine Namen. Die lokale Wiederherstellung erkennt Platzhalter unabhängig von Groß-/Kleinschreibung, Innenabstand und optionaler Nullauffüllung (PERSON:1 / PERSON:01). Generische Auslassungsplatzhalter ([[…]], [[...]]) werden nicht demaskiert; im Journalentwurf gelten sie als unbelegter Inhalt und führen zu journal_generation_not_accepted, nicht zu einem stillen Rohtext-Entwurf. Der Journal-Seed darf keine Beispiel-Klammern [[…]] enthalten, damit das Modell sie nicht in den Text kopiert.

Persistenz und Migration

Bestehende identity_mappings werden nicht gelöscht und nicht pauschal bestätigt. Additive Migration setzt status=legacy_review_required und origin=legacy_auto. Vor lokaler Bereinigung die Datei backend/data/kansho.sqlite kopieren; Wiederherstellung durch Zurücklegen der Kopie bei gestoppter Anwendung. Admin /admin/identities und /api/admin/identities bestätigen, korrigieren, aliasieren, deaktivieren oder entfernen lokal. Kein externer Egress, keine Klartextlabels in Compact-Logs.

Kosten und Diagnose

Mehrere Detect-Calls bei langen Prompts sind zulässig. Der Nutzer akzeptiert die Laufzeit. Compact-Trace ohne Labels: Detect-Provider/Modell, Zeichen, Chunks, full_detection_coverage (nur technische Chunk-Verarbeitung), confirmed_registry_applied, request_local_hits, semantic_identity_guaranteed (im MVP immer false), Entitäten nach Typ, Detect-Aufrufe, Tokens/Kosten/Laufzeit, generate_called, Abbruchgrund. Technisch verarbeitete Chunks sind kein Anspruch auf vollständige semantische Identitätserkennung.

Normalfall nach erfolgreicher Detection: genau ein Generate-Call. Aktiver Klartext in der Modellantwort erzeugt keinen zweiten Generate-Aufruf.

Additiv 2026-08-29: Fehlerantworten der Journal- und Profilreview-API enthalten cost_report: ob Detect/Generate den Anbieter erreicht haben (detect_called, generate_requested, generate_called) und welche Kosten der Anbieter gemeldet hat. Ablehnung vor dem Socket bleibt billed=no. Ein gesendeter Generate-Call ohne Usage ist unknown. Positive Detect- oder Generate-Kosten sind yes. Die Nutzermeldung darf den Providergrund (HTTP-Status, gekürzte error.message) enthalten, nicht den Prompt und nicht die Mapping-Tabelle.

Bestätigte Registry-Zeilen und bestätigte Aliase werden im gesamten gerenderten Egress labelbasiert maskiert, auch wenn Detect denselben Wortlaut an anderer Stelle nicht gemeldet hat. Request-lokale Spans bleiben span-genau. Kurze bestätigte Namen (unter der Detect-Mindestlänge) bleiben maskierbar, sofern sie nicht strukturell unmaskierbar sind (Pronomen, Rollen, Uhrzeiten).

Tests und Live-Qualität

Contract-Tests: backend/tests/test_privacy_detect.py, backend/tests/test_identity_registry.py, backend/tests/test_privacy_response_integrity.py, backend/tests/test_detect_contract_retry.py, backend/tests/test_detect_learning.py. Sie beweisen Schema, Fail-closed, span-genaue Maskierung, Lernmodus-Pause und Datenfluss, nicht semantische Modellleistung. Opt-in: python entity_detect_eval.py --live mit synthetischen Sätzen und exakten erwarteten Spans. Ohne diesen Lauf bleibt die Live-Qualität unbestätigt. Das aktuell konfigurierte openai/gpt-4.1-nano gilt durch reale False-Positive-Vorschläge qualitativ nicht als zuverlässig bestätigt; das Modell wird deshalb nicht stillschweigend gewechselt.

9.6 Übergang: KANSHO_ALLOW_REMOTE_DETECT (2026-09-08)

Additiv. Fachliches Home: ../functional/guardrails.md §22.3.

Default in KANSHO_ENV=production: externes Klartext-Detect bleibt fail-closed (remote_detect_blocked_production). Lokales Detect bleibt erlaubt. Development/Test erlauben remote Detect ohne Extra-Flag.

Operator-Übergang bis Ollama: KANSHO_ALLOW_REMOTE_DETECT=1 (Compose .env, Container neu anlegen). Das Gateway, die Pflicht-Detection vor Generate, ZDR/No-Train für Generate und die Key-Trennung bleiben. Admin kann das Flag nicht setzen. URL und Modell je Stufe bleiben in provider_settings.

Additiv 2026-09-08 (LLM-Profile, LAN-Ollama): llm_profiles speichert benannte URL/Modell-Presets ohne Keys. Jede Stufe (generate / detect) zeigt auf ein Profil; Wechsel kopiert die gespeicherten Felder, statt sie neu einzugeben. RFC1918-, Loopback- und .local-URLs gelten als lokale Trusted Zone. http://192.168.2.144:11434/v1/chat/completions ist damit lokales Detect, kein externes Klartext-Detect. Ollama muss auf dem Host auf 0.0.0.0:11434 lauschen; der Pi muss Port 11434 erreichen.

Additiv 2026-09-08 (lokales Detect, Timeouts): CPU-Ollama braucht für denselben Detect-Prompt deutlich länger als OpenRouter. Remote-Detect bleibt 90s / 1024 Tokens. Lokales Detect wartet 300s und begrenzt auf 256 Tokens. Live-TLS endet auf dem Synology-Reverse-Proxy (192.168.2.63, DSM-Default 60s). Dort Proxy-Lese- und -Sende-Timeout 600s setzen, sonst sieht der Browser HTTP 504, während das Backend weiterläuft. Ein Dialogzug kann danach lokal einen Halte-Impuls gespeichert haben. Die Compose-Frontend-Nginx auf Port 3096 hat bereits 600s.

Status in GET /api/admin/providers: remote_plaintext_allowed, remote_plaintext_reason (non_production | operator_override | blocked), profiles, detect_operating_mode (semantic | learning). Modellauswahl: GET /api/admin/providers/models (OpenRouter-Katalog oder Ollama /api/tags, soft-fail).

9.7 Übergang: Detect-Lernmodus (2026-09-08)

Additiv. Fachliches Home: ../functional/guardrails.md §22.4. Ersetzt weder semantische Detection noch die bestätigte Registry.

Default bleibt semantic: Detect läuft wie bisher, Generate folgt ohne Pause. learning ist ein Admin-Schalter (PUT /api/admin/providers/detect-mode), kein Gateway-Bypass und keine Wortlisten-UI.

Im Lernmodus untersucht Detect weiterhin den vollen gerenderten Generate-Egress. Bevor Generate startet, werden request_local-Spans im aktuellen Nutzersatz zur Bestätigung angeboten (Dialog und Journal-Gespräch, nicht Journal-Generate). Zusätzlich: Nennungen nach Verwandtschaftswörtern der bestehenden Identitätsregel (Frau, Sohn, …) im Nutzersatz, auch wenn Detect sie als Gericht weglässt oder nicht meldet. Andere Großschreibung (Restaurant) wird dadurch nicht angeboten. „Identität“ bestätigt die lokale Registry und zählt einen Identitätssinn. „Nicht schützenswert“ maskiert diese Nennung nicht und zählt den anderen Sinn. Detect-Ausgabe allein speichert weiterhin keine aktive Identität.

Es gibt keine separat zu pflegende Doppeldeutigkeitsliste. Mehrdeutigkeit entsteht, wenn dieselbe Schreibweise beide Sinne hat. Nur dann darf ein lokales Detect-Modell eine Mini-Passage (Ausschnitt um die Nennung) entscheiden. Fehlt ein lokales Modell oder ist die Antwort unbrauchbar, bleibt das Popup. OpenRouter erhält diese Passage nicht.

Nach der Bestätigung läuft Generate mit den geprüften Mappings (precomputed_learning_review), ohne zweiten Detect-Pass. Compact-Diagnose enthält weiterhin keine Klartextlabels. Tests: backend/tests/test_detect_learning.py.

10. Offene Fragen

Übernommen aus guardrails.md §21, hier nicht vorentschieden: Entitätstypen, Quasi-Identifikatoren, Pseudonym-Stabilität, Mehrnutzer-Trennung der Mappings, Verschlüsselung der Mapping-Tabelle, rechtliche DSFA bei Mehrbenutzer-Produktbetrieb.

Additiv 2026-08-28: Wie sollen neue, vom Detect verfehlte und lokal nicht bestätigte Identitäten vor dem Egress behandelt werden, ohne span-genaue Homonyme oder Allgemeinbegriffe dauerhaft zu maskieren? Optionen ohne Vorentscheidung: fail-closed vor Generate, Review der Detect-Vorschläge vor Egress, oder ein zweites lokales Verfahren. Keine Heuristik auf Verdacht.

Additiv 2026-09-08: Der Detect-Lernmodus ist eine Übergangslösung für die Review-Option, nicht der Endzustand. Semantische Detection bleibt Pflicht. Unbekannte Namen sind nicht deshalb harmlos, weil sie nicht auf einer Liste stehen.

11. Querverweise

  • Fachlich: ../functional/guardrails.md
  • Technisch: ai_architecture.md, security.md, voice_and_media.md, platform_extensibility.md