--- title: "Kanshō – Technische Umsetzung des MVP-Journal-Slices" status: "Arbeitsstand" date: "2026-08-25" product_family: "Jinkendo" document_role: "Technical Chapter / MVP Implementation How-To / Module and Runtime Map" parent_document: "technische_zielarchitektur.md" --- # Kanshō – Technische Umsetzung des MVP-Journal-Slices **Kanonisches Home** dafür, *wie* der erste vertikale Slice gebaut ist: Laufzeit, Module, Daten, API, Pfade. Nicht dieses Dokument: - Fachlicher Lieferumfang → `../functional/mvp.md` - Foundation-Regeln → `../functional/implementation_foundation.md` - Fit-Gap (Spec, Abweichungen, offene Punkte gegen Slice und Gesamtziel) → `../functional/mvp_stand_und_abgleich.md` Kapiteldetails (Auth, Gateway-Verfahren, Shell-Muster) bleiben in den jeweiligen technischen Homes. Hier die zusammenhängende Umsetzung. **Stand:** 2026-08-25, Code unter `frontend/` und `backend/`. Architecture-Correction-Pass am selben Tag (relationale Source-Refs, Selection-Specs, begrenztes Space-Source-Retrieval, Detect-Env, Response Validation, Konsolidierungssignale, `/dialog` als Admin-Harness). --- # 1. Laufzeit Lokal, eine Instanz, ein Profil-Nutzer nach Setup: | Prozess | Stack | Port | Einstieg | |---|---|---|---| | Frontend | React 18, Vite, React Router 6, PWA-Plugin | **5188** (`strictPort`) | `frontend/` → `npm run dev` | | Backend | FastAPI, Python 3.12, Uvicorn | **8018** | `backend/` | | Persistenz | **SQLite** Datei | — | `backend/data/kansho.sqlite` | | Medien | lokale Dateien | — | `backend/data/media//` | | Secrets | Env | — | `backend/.env` (Keys nie in der DB) | Proxy: Vite leitet `/api` an 8018. CORS erlaubt `localhost:5188`. **Abweichung vom technischen Zielrahmen (lokal):** `product_frame_and_stack.md` nennt PostgreSQL. Der Slice auf dem Windows-Checkout bleibt SQLite (`5188`/`8018`). **Additiv 2026-09-07:** Docker/Server nutzt PostgreSQL 16 (`KANSHO_DB_BACKEND=postgres`). Stores behalten SQLite-förmiges SQL; `sql_compat.py` übersetzt zur Laufzeit. Nummerierte Dateien ab `backend/migrations/022_*.sql`. Siehe `runtime_and_deploy.md` und `docs/DEPLOYMENT.md`. **Additiv (Qualitätssystem 2026-09-07):** Kanonischer Backend-Runner ist pytest im Dev-Checkout nach erfolgreichem Deploy. `python -m pytest tests -m "not slow"` gegen `kansho_test`. `scripts/test-mvp.ps1` ist nur noch der lokale Wrapper (Unit ohne Postgres, Integration nur mit `DB_NAME=kansho_test`). SQLite-Testdateien sind abgelöst; `test_local_backup.py` prüft weiter das Windows-Backup-Format. Health: `GET /api/health`. --- # 2. Schichten und Invariante ```text PWA (React) → /api Session-Cookie FastAPI-Router (kein Prompt-Bau im Frontend) → journal_policy / stores → context_builder + retrieval (intern, Klartext, nie Egress) → privacy_gateway (einziger generativer Egress) Policy → Maskierung → Detect (Testphase) → Generate-Call → Validierung → Demask → SQLite + media/ ``` Persönlicher Kontext geht nicht direkt an OpenRouter. Router rufen kein LLM. Fail Closed: kein unsicherer Generate-Fallback. Zwei Provider-Rollen (`provider_settings`): `detect` (Maskierung) und `generate` (Dialogzug und Journalentwurf). URL/Modell/Flags in der DB, Key in `.env`. Admin: Schnittstellen. --- # 3. Backend-Module Einstieg: `backend/main.py` (Router: auth, users, dialogue, journal, prompts, placeholders, subscription, admin). | Modul | Verantwortung | |---|---| | `routers/journal.py` | Produkt-API `/api/journal` | | `routers/dialogue.py` | Low-Level-Source `/api/dialogue`; derselbe Turn-Pfad | | `dialogue_turn.py` | Ein generativer Call, JSON `{operation, impulse}`, lokale Register-/Wächter, optional Repair | | `journal_generate.py` | Expliziter Draft: Stufe 1 lokales Quellenartefakt, Stufe 2 Narration | | `journal_generation_policy.py` | Journalspezifische Ausgabeeinstellungen; wählt benannte Richtlinienausprägungen, keine Modelltemperatur | | `routers/generation_instructions.py` | Admin-API für versionierbare Generierungsrichtlinien | | `journal_reconstruct.py` | Parser/Validator und `local_verified_artifact`; kein Runtime-Modellpfad | | `prompt_budget.py` / `model_catalog.py` | Kontextfenster, Output-Reserve, konservative Tokenschätzung | | `journal_store.py` | Days, Drafts, Entries, Versionen, Scratch | | `journal_policy.py` | Explizites Generate, Origins, Konsolidierungsangebot aus lokalen Signalen | | `journal_body.py` / `journal_shape.py` | Titel, Markdown-Body, lokale Nachformung | | `context_builder.py` | Interner Prompt-Kontext über Selection-Specs, keine Recency-API | | `retrieval.py` | Austauschbare Specs: `day_messages`, `space_entries`, `space_recent_sources`, `writing_profile`. Recency/SQL-Kappen sind Implementierung. | | `conversation_signals.py` | Lokale operative Metadaten (`narrative_mode`, `reflection_depth`, `current_focus`), kein Extra-LLM | | `privacy_gateway.py` | Egress; Response Validation vor Demask | | `entity_detect.py` / `pronoun_bind.py` / `identity_store.py` | Detection, Dialog-Pronomen, Mapping (Klasse A) | | `writing_profile_store.py` | Lokaler Journal-Stilbrief, Facets, Governance, kein Extra-LLM, keine Dialogsteuerung | | `profile_review.py` | Lokale Evidence-/Drift-Erkennung, Review-Pakete, API- und Paste-Kanal; kein Call nach jedem Dialog | | `profile_analysis.py` | Gemeinsamer ProfileAnalysisPackage-/Result-Vertrag, Evidence-Auswahl, Copy/Paste-Prompt | | `writing_profile_infer.py` | Lokale Heuristik für Facets, kein LLM | | `media_store.py` | Upload, Datei, Token im Body | | `providers.py` / `provider_settings.py` | Provider-Rollen | | `debug_store.py` | Opt-in lokale Admin-Testspur (`app_settings`, `debug_runs`); Default aus | | `db.py` + `schema.sql` | SQLite, Migrationen | Prompts in der DB, nicht im Anwendungscode: `mvp.dialogue_turn`, `mvp.journal_reconstruct`, `mvp.journal_generate`, `mvp.entity_detect`, `mvp.profile_review`. Seed: `backend/config/prompts.seed.json`. Unangetastete Templates aktualisiert `init_db`. --- # 4. Daten (Ist, nicht Ziel-PostgreSQL) Schema: `backend/schema.sql`. Isolation über `profile_id`. **Additiv 2026-09-07:** Dieselbe `schema.sql` ist Greenfield für Postgres (Dialekt nur `datetime('now')` → `CURRENT_TIMESTAMP::text` in `db_init.py`). SQLite-History 001–021 wird auf leerem Postgres als applied erfasst, nicht als ALTER nachgespielt. Persönliche Daten kommen nicht über den Alltagspfad, sondern über `sqlite_to_postgres.py`. **Rahmen / Layer 0:** `profiles`, Auth-`sessions`, `usage_sessions`, `conversations`, `messages`, Thread-/Space-Hüllen, `derived_records` (ungenutzt für Journal-Writes), `identity_mappings`, Prompt-/Feature-/Provider-Tabellen. **Additiv 2026-08-28:** `app_settings` (Instanzschalter, derzeit `debug.persist_traces`) und `debug_runs` (Admin-Testspur je Schritt, Profil-isoliert, keine Mapping-Tabelle). **Journal-Slice:** | Tabelle | Rolle | |---|---| | `spaces` | Sichtbarer Reflection Space (`visibility='user'`) | | `journal_days` | `(space_id, calendar_date)`; `scratch_json` lokal | | `journal_drafts` | Derived; neue Generierung historisiert den Draft | | `journal_entries` | Current über `current_version_id`; Soft-Delete `deleted_at` | | `journal_entry_versions` | History; Restore hängt eine Version an | | `journal_draft_source_refs` / `journal_entry_version_source_refs` | Relationale Provenance (Conversation/Message-IDs). JSON-Listen sind Altbestand und werden beim Start migriert. | | `media_assets` | Datei + Kind; Referenz nur im Body `![…](kansho-media:)` | | `writing_profiles` / `writing_profile_sources` | Current Brief | | `journal_generation_settings` | Profildefaults der vier Journal-Ausgabeeinstellungen (0–100). Nicht Writing Profile. | | `generation_instruction_fragments` | Administrierbare Anweisungstexte je Purpose/Slot/Stufe. Seed aus JSON, gleiches Governance-Muster wie `ai_prompts`. | | `journal_generation_selection` | Profildefaults als vier Ausprägungs-IDs. Nicht Writing Profile. | | `generation_guidelines` | Versionierbare benannte Ausprägungen je Purpose/Slot (`draft`/`active`/`archived`). Seed aus JSON. | | `writing_profile_evidence` / `writing_profile_reviews` / `writing_profile_versions` | Review-Pipeline: lokale Evidenz, Paket/Ergebnis, Versionen | Assignment: `conversations.space_id` / `journal_day_id` sind Felder, nicht Identität. `space_id` ohne `journal_day_id` ist zulässig (Space ≠ Journal-Container). `conversations.space_id` ohne FK. Kalendertag ≠ `messages.created`. Operative Dialogsignale liegen als Felder auf `conversations`, nicht als Writing Profile. Details: `memory_storage_and_offline.md` §6.1–6.2. --- # 5. API (Produkt) Prefix `/api/journal`, Session-Auth, Router `backend/routers/journal.py`. Vollständige Liste: `backend_and_api.md` §5.1. Kernpfade: 1. **Dialogzug** `POST /days/{id}/conversations/{cid}/turn` und `POST /api/dialogue/conversations/{id}/turn` Context Builder → Gateway (`purpose=dialogue_turn`) → ein Generate-Call → Impuls speichern. Pronomenbindung nur hier in `user:`-Zeilen. 2. **Journalentwurf** `POST /days/{id}/generate` Nur explizit. Optional `conversation_ids`. Ohne Auswahl kein stilles Mergen. Optional `generation_policy` (vier Ganzzahlen 0–100) und `remember_generation_policy`. Ohne Snapshot gelten gespeicherte Profilwerte. Mit Snapshot gelten die Request-Werte; Persistenz nur bei `remember_generation_policy=true`. `GET /generation-settings` liest die Profildefaults. Stufe 1 lokal (`local_source_artifact`), Stufe 2 ein Gateway-Zweck `journal_generate`. Pronomen ungebunden. Entry-`current_version_id` bleibt unangetastet. UI öffnet `?draft=1`. 3. **Speichern** `POST /entries` Mit `entry_id` + `origin=accepted_draft`: neue Version desselben Entry. Sonst neuer Entry oder `user_edit`. 4. **Restore** `POST /entries/{id}/restore` → neue Version, Origin `restore`. 5. **Scratch** `PATCH /days/{id}/scratch` → nicht im Context Builder, Test: Token nicht im Egress. 6. **Media** Upload/GET/DELETE unter dem Entry. 7. **Profile Review** `GET/POST /writing-profile/review…` → lokale Evidenz, explizite API- oder Paste-Review, kein Call am Dialogzug. `include_existing` existiert in der API und in der UI; Default ist aus. Die Nutzerfassung bleibt unangetastet. Bei gesetztem Flag gilt die gespeicherte Entry-Fassung; ein liegengebliebener Generate-Draft wird nur verwendet, wenn es noch keinen Entry gibt. Fehlerkörper oft `{detail: {code, message}}`, Rahmen will `{detail: string}` — Abweichung, Fit-Gap. --- # 6. Frontend-Umsetzung Shell: `frontend/src/App.jsx`, Nav `config/appNav.js` (Start / Journal / Einstellungen; Admin extra). `/dialog` ist nicht in der Produkt-Nav. Breakpoint 1024px: Bottom-Nav vs Sidebar. Details: `frontend_pwa_shell.md` §6.1. | Route | Seite | Technik | |---|---|---| | `/` | `HomePage.jsx` | Kontinuität: Fortsetzen des letzten aktiven Journal Day oder Space-Einstieg | | `/dialog` | `DialoguePage.jsx` | **Admin-only Dev/Test-Harness**, nicht in der Produkt-Nav. Unzugeordnete Conversations. | | `/journal` … `/journal/:spaceId/:dayId` | Spaces, Space, `JournalDayPage.jsx` | Chat, Generate, `DayScratch.jsx` | | `…/entry` | `JournalEditorPage.jsx` | TipTap, Markdown in `journal/document.js`, Dirty-Flag `context/UnsavedChanges.jsx` | | `…/source/:conversationId` | `JournalSourcePage.jsx` | Quelldialog | | `/settings` | `SettingsPage.jsx` | Writing/Interaction, Review-Paket (API oder Copy/Paste) | Editor speichert Markdown, nicht HTML. Medien-Token bleiben lokal. Dirty: In-App-Links, Abmelden, `beforeunload`; Browser-Zurück nicht hart blockiert. --- # 7. Zwei fachliche Calls, drei technische Schichten | Schicht | Rolle | Call | |---|---|---| | Maskierung | `detect` | Vollständige semantische Detection des Generate-Egress. Externes Klartext-Detect in Development/Test; in Production default-off, optional Operator-Übergang `KANSHO_ALLOW_REMOTE_DETECT`. Ziel: lokales Detect-Modell. Kein Pattern-Fallback. Admin-Modus `semantic` (Default) oder `learning` (Übergang, Bestätigung vor Generate). | | Dialogzug | `generate` | Operation + Impuls | | Journalentwurf | `generate` | Explizit, getrennt | Explizite Writing-Profile-Review ist ein weiterer Generate-Zweck (`profile_review`), nicht der Dialogzug und nicht nach jedem Turn. **Additiv 2026-08-25:** Der Journalentwurf ist fachlich zwei Stufen (Quellenartefakt, dann Narration). Der Runtime-Pfad ruft dafür kein Rekonstruktionsmodell mehr auf. Dialogzug bleibt ein Call. Details: §16. **Additiv 2026-08-26 (Narrationsvertrag):** Stufe 2 formuliert eigenständige Journalprosa. Das `VerifiedArtifact` bestimmt, was gesagt werden darf; das Writing Profile, wie es gesagt wird. Der Quellwortlaut ist keine Ausgabevorlage. `shape_journal` überschreibt akzeptierten Modelltext nicht mit Dialogzeilen. Der aktuelle Tagesdialog ist keine Stilquelle desselben Laufs. Interner Context-Builder (Klartext, lokal): fordert Selection-Specs an (`day_messages`, `space_entries`, `space_recent_sources`, `writing_profile`). Recency-Kappen (Day 80 Messages für den Dialogzug; Journal-Generate nutzt Tokenbudget statt der 80er-Kappe, Overflow bricht ab; 5 Space-Entries × 400 Zeichen; 3 Space-Source-Conversations × 400 Zeichen User-Ausschnitt) liegen in `retrieval.py`, nicht in der Context-Builder-API. Dialogzug: aktuelle Conversation primär, plus begrenzte frühere Original-Conversations desselben Space (ohne denselben Journal Day voll zu laden). Generate-Stufe 1: lokale Day-Messages, vollständiges `VerifiedArtifact`. Generate-Stufe 2: Writing Profile plus Artefakt, ohne den vollen Dialog. `WritingProfile` steuert Journaltext, nicht den Dialogue Kernel; der Turn hat einen leeren `interaction_hint`-Slot. Retrieval austauschbar, keine Embeddings. Gateway-Verfahren und offener Security Layer: `privacy_gateway.md` §9.1–9.2 und §9.5. --- # 8. Tests (technisch) | Suite | Ort | |---|---| | Rahmen, Auth | `backend/tests/test_frame.py` | | Journal-Slice, Scratch-Egress, Versionsschutz | `backend/tests/test_mvp_journal.py` | | Architecture Correction (Provenance, Space-Source, Detect-Env, Kernel-Grenzen) | `backend/tests/test_architecture_correction.py` | | Detect, Pronomen Dialog vs Journal, Response Validation | `backend/tests/test_privacy_detect.py` | | Detect-Vertragsretry, azyklische Diagnose, Persistenzschutz | `backend/tests/test_detect_contract_retry.py` | | Request-scoped Maskierungsmanifest / Pre-Egress | `backend/tests/test_privacy_manifest.py` | | Journal-Narration (Faktentreue, nicht Wortlaut) | `backend/tests/test_journal_narration.py` | | Journal-Editorial Modes, Profil, Stilreferenzen | `backend/tests/test_journal_editorial.py` | | Journal-Generation-Policy | `backend/tests/test_journal_generation_policy.py` | | Journal-Stilkontext der Stilanwendung | `backend/tests/test_journal_style_context.py` | | Journal-Legacy-Stimmen und immutable Seeds | `backend/tests/test_journal_style_legacy_immutable.py` | | Journal-Eval-Vertrag (kein Live-Qualitätsbeleg) | `backend/tests/test_journal_eval.py` | | Shape ohne Pronomenheuristik | `backend/tests/test_journal_shape.py` | | Writing Profile | `backend/tests/test_writing_profile.py` | | Profile Governance | `backend/tests/test_profile_governance.py` | | Profile Review Pipeline | `backend/tests/test_profile_review.py` | | Profile-Review-Fehlergrund und Kosten | `backend/tests/test_profile_review_errors.py` | | Journal-Budget / zweistufige Generierung | `backend/tests/test_journal_budget.py` | | Persistierte Admin-Testspur | `backend/tests/test_debug_persist.py` | | Lokales Backup/Restore | `backend/tests/test_local_backup.py` | | Papierkorb / Purge | `backend/tests/test_journal_trash.py` | | Erster Journal-Impuls | `backend/tests/test_journal_opening.py` | | Writing-Profile-Hülle / Initial Build | `backend/tests/test_profile_review.py`, `backend/tests/test_profile_governance.py` | | Markdown-Runde | `frontend/src/journal/document.test.js` | | Lokaler Wrapper | `scripts/test-mvp.ps1` (pytest, kein kanonischer Runner) | Tests beweisen API-Verträge und Invarianten, nicht Dialogqualität. **Additiv 2026-09-07:** pytest ist der Backend-Runner (`backend/pytest.ini`). Gitea führt `python -m pytest tests -m "not slow"` nach erfolgreichem Dev-Deploy auf `kansho_test` aus. `scripts/test-mvp.ps1` wrappt denselben Aufruf lokal. Unit-Marker laufen ohne Postgres; Integration nie gegen `kansho_dev`. Schema einmal pro Lauf, dazwischen nur Daten-Truncate — nicht 22× `schema.sql`. --- # 9. Technische Abweichungen und nicht gebaut Nur die **technische** Lage. Bewertung gegen Spec und Vision: Fit-Gap. | Thema | Ist | Zielrahmen / Produktiv | |---|---|---| | DB | SQLite Datei | PostgreSQL | | Detect | Semantische Request-Detection; Chunking; fail-closed ohne vollständige Abdeckung; Detect-Treffer nicht auto-persistiert. Externes Klartext-Detect in Development/Test; Production default-off, optional `KANSHO_ALLOW_REMOTE_DETECT` bis lokales Detect. | lokales Modell (Ollama) als Ziel | | Fehlerkörper | oft Objekt | `{detail: string}` | | `/dialog` | Admin-Harness, aus der Produkt-Nav entfernt | nicht MVP-Screen | | Response Validation | blockiert Klartext-Identität in der Rohantwort vor Demask; keine Quasi-Identifikatoren | `privacy_gateway.md` §9.2 voll | | Security Layer | Pfad da; Minimierung/Audit/Encryption fehlen weiter | `privacy_gateway.md` §9.2 | | Offline-Sync, Voice-Egress, Compose-Prod | ungebaut | jeweiliges Kapitel | Nicht im Code: Threads-UI, semantisches Retrieval, Session-Summaries, Related-Space-UI, lokales Detect-Modell, Obsidian/mindnet/Kairo-Handoff. Papierkorb-UI, Entry-Liste, lokales Backup/Restore und erster Journal-Impuls: 2026-08-25. --- # 11. Korrigierter Ist-Stand vs. bewusst offene Target-Fähigkeiten Additiv zum Slice, 2026-08-25. Kein Target-Model-Vorbau. **Korrigiert im Ist:** - Provenance von Draft und Entry-Version ist relational (`journal_*_source_refs`); JSON-ID-Listen werden verlustfrei migriert. Source-Messages bleiben unangetastet. - Context Builder spricht Selection-Specs; Recency bleibt die aktuelle Retrieval-Heuristik, nicht die fachliche API. - Dialogzug kann begrenzte jüngere Original-Conversations desselben Space sehen, ohne denselben Journal Day voll zu laden. - Externes Klartext-Detect ist kein Produktmodus (`KANSHO_ENV=production` ohne lokales Detect fail-closed). **Additiv 2026-09-08:** Operator darf mit `KANSHO_ALLOW_REMOTE_DETECT` den Übergang bis Ollama explizit öffnen; ohne Flag bleibt der Block. **Additiv 2026-09-08:** RFC1918-Ollama gilt als lokal; LLM-Profile speichern URL/Modell je Endpunkt. Lokales Detect bleibt austauschbare Rolle. Detect-Ausgabe persistiert keine aktive Identität. - Response Validation blockiert Klartext-Identität vor Demask. Dialog-Egress maskiert den ganzen Prompt inkl. Opening/Space-Kontext; ein Leak bricht den Zug nicht leer ab. Journal-Generate bricht bei einem Leak nach Retry nicht ab: lokales Quellenartefakt bzw. lokaler Entwurf, die leckende Modellantwort wird nicht verwendet. - Konsolidierung nutzt lokale Signale, nicht `count >= 2`. - `/dialog` ist Admin-Harness, nicht Produkt-IA. - `WritingProfile` hängt nicht am Dialogue Kernel; `interaction_hint` ist ein leerer Slot. - Conversation kann einem Space ohne Journal Day zugeordnet werden. **Bewusst offen (nicht in diesem Pass gebaut):** - Semantisches/globales Retrieval, Threads, Self Model, Related Spaces, mindnet - Interaction-Profile-Engine, UI und Persistenz - Reflection-Space-Typen, Journal-Default am Gesprächsabschluss - Lokales Detect-Modell, Audit ohne Prompt, Encryption, inhaltliche Minimierung - Contextual Continuation als Ziel-IA (Home ist eine journalspezifische Fortsetzen-Keimzelle) --- # 17. Freeze Candidate, Backup, Papierkorb, Opening (2026-08-25) Additiv zum Slice. Fachliche Abnahme: `../functional/mvp_freeze_candidate.md`. - Lokaler Wrapper `scripts/test-mvp.ps1` (pytest; UTF-8, isolierte Temp-Daten, keine Live-Keys; Fake nur in den Suites, die ihn setzen). Kanonisch: Gitea pytest auf `kansho_test`. - Backup: `backend/local_backup.py`, Einstieg `scripts/backup-local.ps1`. SQLite-Backup-API, Medien, Manifest, keine Secrets. Restore bestätigt, prüft Checksummen, legt Sicherheitsbackup an, bricht bei geöffneter DB ab. - Papierkorb: `GET /spaces/{id}/entries`, `GET /spaces/{id}/trash`, `POST /entries/{id}/undelete`, `POST /entries/{id}/purge`. - Erster Impuls: `journal_opening.py`, derselbe Gateway-Pfad, `{{opening_hint}}`. **Additiv 2026-09-03:** Vorhaben nur aus dem user-Dialog des vorigen Kalendertags (`calendar_date` an `space_recent_sources`); Journal-Entries und ältere Recency sind kein Opening-Mandat. Der Opening-Call setzt `include_space_recency=False`. Tests: abgeschlossener Entry mit „nächsten Tag“ und Plan von vorgestern → `local_neutral`; „Morgen wollen wir …“ nur am Folgetag → Modellpfad. **Additiv 2026-09-07:** Prefix-Recency (5×400 Entries, 3×400 Source-Conversations) bleibt im *laufenden* Dialogzug; das ist kein Opening-Mandat. Ein Nachsatz mit Morgenuhrzeit darf den Folgetag nicht über den Entry-Anfang steuern. - Home: `GET /continuity`. Bewusst nicht: Docker, Postgres, Ollama, Verschlüsselung, Dialogmedien als Quelle, Space-Entries als Generate-Fakten. --- # 12. Profile Governance (2026-08-25) Additiv zu §11. Kein Self Model, keine Intent-Engine, kein ML-Training. **Writing Profile** - Ein Profil als Hülle: Core, optionale Facets, dynamische Traits. Die früheren Stilschlüssel sind Seed, nicht Schema. `compiled_brief` bleibt abgeleitete Sicht aus Traits und zeitgestempelten Quellen. - Governance `learning` | `advising` | `frozen`. Automatische Pfade (Turn, Save, Import, Bootstrap) überschreiben keine Locks und keine manuellen/angenommenen Facets. `frozen` hält Facet-Werte. Explizites Rebuild ehrt Locks. - Rückwirkender Bootstrap aus gespeicherten Current-Entries. Unredigierte Drafts bleiben keine Stilquelle. Dialog ist schwächer und entfällt als Stimme, sobald Entries existieren. **Interaction Profile** - Getrennt, Default `advising`. Produktdefaults (Antwortlänge, ein Anschlussimpuls, Zuhören bei Erzählung) sind gelabelt und überschreibbar. - `{{interaction_hint}}` füllt sich aus Defaults + persönlichen Prefs + aktuellem Dialogue State. Nicht-Widersprechen ist kein Lernsignal. `propose_observation` legt höchstens Suggestions an und wird nicht aus `run_turn` aufgerufen. - Keine Intent-Taxonomie. Reflection Intent bleibt ungebaut. Dialogue State (`narrative_mode`, `reflection_depth`, `current_focus`, `emotional_intensity`, `long_story`) bleibt automatisch. Kleine Settings-Erweiterung: Lifecycle/Initial Build, dynamische Traits statt festem Facet-Raster, Prefs, Vorschläge. Keine Profile-Management-App. JSON-Portabilität (additiv): `kind` `kansho.writing_profile` / `kansho.interaction_profile` / `kansho.profiles`, `format_version` 1. Restore ersetzt Facets/Prefs und Governance ausdrücklich; Journalquellen, Dialoge und Identity-Mapping gehören nicht ins Dokument. --- # 13. Profile Review Pipeline (2026-08-25) Additiv zu §12. Keine neue Profil-Engine. Nach Save/Import/Turn werden semantische Traits **nicht** neu berechnet. Der frühere Bootstrap-Infer über Zählfacetten entfällt; Quellen werden nur assembliert. Kontinuierliche Queue erst nach bestätigtem Initial Build (§14). Danach: 1. lokale Evidence-/Drift-Erkennung (billig, ohne LLM) 2. Review Candidates in `writing_profile_evidence` 3. semantische AI Review nur explizit, Evidenzen gebündelt 4. Governance entscheidet die Übernahme (`learning` wendet unlocked an, `advising` queued Suggestions, `frozen` nicht) 5. `writing_profile_versions` hält den Stand nachvollziehbar Writing-Trigger: starke User-Änderung eines AI-Drafts, heuristische Abweichung, Stichprobe längerer Dialogtexte (Cooldown, kein LLM), periodische Bereitschaft nach Zeit/Volumen, explizite Nutzer-Review. AI Review vergleicht Evidenz mit dem bestehenden Profil und liefert Änderungsvorschläge (`keep`/`update`), kein komplettes neues Profil. Paket unterscheidet `core` und `facet` und darf repräsentative Source-Beispiele an Merkmalen führen. API (`POST /writing-profile/review/api`) und Copy/Paste (`POST .../paste` plus `.../import`) nutzen denselben Vertrag: `kansho.profile_review_package` / `kansho.profile_review_result`. Paste erzeugt lokal einen kopierbaren Prompt; das Ergebnis kommt strukturiert zurück. Interaction-Präferenzen entstehen nicht aus fehlendem Widerspruch oder normalem Gesprächsverlauf. **Additiv 2026-08-29 (Fehlergrund und Kosten bei API-Analyse):** `POST /writing-profile/review/api` und `.../initial-build/api` liefern bei Gateway- und Parserfehlern `detail.code`, `detail.message`, optional `http_status` / `provider_message` und `cost_report` (`billed` `yes`|`no`|`unknown`, Detect-/Generate-Kosten soweit der Anbieter sie gemeldet hat). Ein gültiger Generate-Call, dessen JSON lokal nicht übernommen wird, bleibt ein Fehler mit `generate_called=true` und ausgewiesenen Kosten. Die Settings-UI zeigt Grund und Kostennotiz, nicht nur „Anfrage fehlgeschlagen“. Mapping, Klartext und Promptkörper gehören nicht in diese Fehlerantwort. Tests: `backend/tests/test_profile_review_errors.py`. Das statische Facet-Raster aus §12 ist **nicht** die Profilstruktur. Semantische Traits und der Initial Profile Build: §14. Kanonischer Analysevertrag, Copy/Paste als Testpfad und Import ohne Auto-Apply: §15. Die älteren `kind`-Namen bleiben als Legacy lesbar. --- # 14. Writing-Profile-Hülle und Initial Build (2026-08-25) Additiv zu §12 und §13. Keine neue große Engine. Stabile Hülle, nicht ein geschlossenes Merkmalsraster: 1. Core 2. optionale context/output-Facets 3. darin dynamische semantische Traits (datengetriebene Slugs) 4. Evidence References 5. Representative Exemplars 6. Governance 7. Versioning `rhythm`, `humor`, `detail`, `chronology` und verwandte Keys bleiben **Seed-/Ordnungshilfe** (`seed_catalog`). UI, Persistenz und Review binden sich nicht an diese Liste. Lokale Heuristiken (`writing_profile_infer`) sind Mess- und Triggerhilfe; sie schreiben keine Traits. Lifecycle: `uninitialized` → `initial_pending` → `confirmed`. Quellen (Journal und Import) tragen `occurred_at` und `context_hint`. Neuere Texte wiegen stärker für die aktuelle Ausprägung, ältere belegen Langzeitmerkmale. Urlaubstagebücher mappen auf `autobiographical_journal`. Ein globaler Core wird nur geschrieben, wenn der Korpus unterschiedliche Quellenarten trägt. Initial Profile Build liest den historischen Korpus (nicht nur zwei Entries). Review-Aktionen folgen Existing-before-New: bestätigen, präzisieren, Scope ändern, zusammenführen/aufteilen, erst dann neu anlegen. Kontinuierliches Learning/Advising (Queue, Drift, Dialog-Sample) startet erst nach bestätigtem Build. API additiv: `POST /writing-profile/corpus`, `GET /writing-profile/corpus`, `PATCH /writing-profile/traits/{slug}`, `POST /writing-profile/initial-build/paste`, `POST /writing-profile/initial-build/api`. Paketvertrag bleibt `format_version` 1 mit zusätzlichen Feldern `mode`, `corpus`, `existing_before_new`, `seed_catalog`. Tests: `backend/tests/test_profile_governance.py`, `backend/tests/test_profile_review.py`. --- # 15. Profile Analysis Export / Import (2026-08-25) Additiv zu §13 und §14. Keine zweite Profilhülle, kein Auto-Apply. Zwei Modi, ein Vertrag für API und Copy/Paste: - `initial_build` — vollständiges Profile Proposal; bestehende Struktur zuerst; Strukturänderungen nach Existing-before-New; gedacht auch für Bootstrap aus einem bereits kontextualisierten externen Chat. - `review` — inkrementeller Vergleich neuer Evidence mit dem bestehenden Profil (`keep` / `update` / `add` / `remove` / `reclassify` / `merge` / `split`). Kein komplett neues Profil bei jeder Review. Paket: `kansho.profile_analysis_package` (`format_version` 1). Enthält Modus, Target `writing`, bestehendes Profil samt Core/Facets/Traits, Governance, repräsentative Kanshō-Evidence und Exemplare, Source-Referenzen, lokale Messsignale nur als Hilfsinformation, semantische Aufgabenstellung, `expected_result`. Legacy-`kind` `kansho.profile_review_package` bleibt lesbar. Ergebnis: `kansho.profile_analysis_result`. `initial_build` darf ein vollständiges `profile`-Proposal mitliefern; `review` beschreibt primär `changes`. Legacy-`kind` `kansho.profile_review_result` bleibt lesbar. Copy/Paste ist in der Testphase ein vollwertiger Ausführungsweg, kein Debug-Hilfsmittel. Kanshō erzeugt aus dem Package einen kopierbaren Prompt. Das externe Modell darf authentische Nutzertexte aus dem bestehenden Chat ergänzend nutzen und muss das als `evidence_basis: external_chat_history` kennzeichnen. Keine erfundenen Quellen, Beispiele oder Kanshō-Source-IDs. Externe Chat-Historie ist Provenienz, kein interner Source-Layer. Evidence-Auswahl bleibt begrenzt (`KANSHO_PROFILE_PACKAGE_CHARS`, Default 12000): aktuelle Texte, ältere Baseline, unterschiedliche Kontexte, keine unlimitierte Volltextsammlung, keine Trigger-/UI-Metatexte als Stil-Evidence. Import niemals direkt als Current Profile. Ablauf: JSON einlesen, Schema prüfen, unbekannte Referenzen listen, Proposal gegen den aktuellen Stand zeigen, einzelne Änderungen übernehmen oder ablehnen oder alles übernehmen; bei `initial_build` zusätzlich „Als neue Profil-Baseline übernehmen“. Erst danach entsteht eine neue Profilversion. Der Current Brief ist abgeleitetes Runtime-Artefakt, nicht Source of Truth, nicht Export, nicht Import, nicht Proposal. Nach Übernahme kompiliert der Brief-Compiler neu; für Journal Generate nur relevanter Core, `autobiographical_journal`-Facet-Deltas und wenige Exemplare, nicht das volle historische Korpus. Ein vollständiges Proposal darf `core` / `facets` / `traits` auch oben im Resultat stehen; `summary` gilt als Wert. Externe Provenienz wie `dayone_export` wird als `external_context` gelesen, nicht als Kanshō-Quelle. Ein Resultat ohne übernehmbare Änderungen wird abgelehnt statt still verschluckt. API additiv: `POST /writing-profile/review/accept`, `POST /writing-profile/review/reject` (optional `indexes`). Settings: „Profil initial erstellen“ / „Profil überprüfen“ → Prompt kopieren → Ergebnis einfügen → Vorschläge prüfen. Tests: `backend/tests/test_profile_review.py`. --- # 16. Journal-Budget und zweistufige Generierung (2026-08-25) Additiv zu §7 und §15. Keine stillschweigende Kürzung autobiografischer Fakten. Chunk-and-Merge bleibt eine spätere Option, ist in diesem Schritt nicht gebaut. ## 16.1 Ablauf **Additiv 2026-08-26:** Der historische Ablauf mit zwei maskierten Gateway-Calls (`journal_reconstruct` dann `journal_generate`) gilt nicht mehr für den Runtime-Pfad. Stufe 1 ist lokal; nur Stufe 2 geht nach außen. Der ältere Text darunter bleibt als Herkunft der Claim-Bindung und Provenienzregeln. Explizites Generate durchlief zuvor zwei maskierte Gateway-Calls, beide Klasse B, Fail Closed, ZDR unverändert: 1. **Stufe 1 – inhaltliche Rekonstruktion** (`mvp.journal_reconstruct`, `purpose=journal_reconstruct`). Eingabe: budgetierter Tagesdialog (alle Nutzerzeilen vollständig; vorausgehende Assistant-Zeile nur als Antwortkontext derselben Conversation). Jede nichtleere Nutzerzeile erhält lokal eine stabile Quellen-ID (`u1`, `u2`, …), Assistant-Zeilen `a1`, `a2`, …. Ausgabe: strukturiertes JSON mit `source_order` (lokale Quellenreihenfolge) und `chronology` (Ereignischronologie, nur umordenbar bei expliziten Zeit-/Reihenfolgeangaben). Lokal fail-closed validiert: vollständige Abdeckung der Nutzer-IDs, keine Umordnung oder Neunummerierung vor der Prüfung, kein Umschreiben ungültiger `source`-Werte, wörtliche `evidence` in der zitierten Nutzerzeile, keine Assistant-Tatsachen, Uhrzeiten/Unsicherheiten/Widersprüche/Korrekturen/Planänderungen bleiben. Ungültige Rekonstruktion geht nicht an Stufe 2; es gibt keine stillen Auto-Reparaturen. 2. **Stufe 2 – persönliche Narration** (`mvp.journal_generate`, `purpose=journal_generate`). Eingabe: validierte Rekonstruktion, kompakter aufgabenspezifischer Writing Brief, optional bestehender Text. Kein erneutes Mitschicken des vollen Tagesdialogs. **Additiv 2026-08-25 (Claim-Bindung):** Chronologieeinträge tragen keine freien Faktenlisten mehr. Jedes inhaltliche Element ist ein Claim `{kind, evidence}` mit eigener, lokal geprüfter Evidence: ein wörtlicher Ausschnitt genau der genannten Nutzerzeile. `source` ist Pflicht und nur der exakte Wert `user`. Legacy-Felder (`events`, allgemeine `evidence`, …) werden abgelehnt. Eine gültige Evidence legitimiert keine zweite, unbelegte Behauptung. Assistententext ist niemals Evidence. Uhrzeiten in `time` müssen ein reines Zeitstoken sein (`6:00`, `06:00`, `6:00 Uhr`) und in derselben `source_id` vorkommen; vertauschte Uhrzeiten sind `time_source_mismatch`. Chronologische Umordnung nur, wenn jede dafür verwendete Uhrzeit source-lokal bestätigt ist und die Minutenfolge nicht abfällt; gleiche oder relative Angaben bleiben in Quellenreihenfolge (`reorder_unjustified`). Top-Level-`contradictions` / `uncertainties` / `plan_changes` / `corrections` sind sourced `{source_id, evidence}`. Stufe 2 konsumiert ausschließlich diese geprüften Ausschnitte plus bestätigte Zeittoken. Damit gilt: kein Fakten-, Zeit-, Gefühls-, Wahrnehmungs-, Bewertungs-, Zitat-, Korrektur- oder Planänderungselement erreicht Stufe 2, ohne dass seine Herkunft aus der angegebenen Nutzernachricht lokal überprüft wurde. Stufe-2-Narration kann stilistisch umformulieren; sie erhält keine unbestätigten Modellparaphrasen als Inhaltsquelle. **Additiv 2026-08-25 (Provenienz / Vollständigkeit):** Die Claim-Bindung allein sichert Herkunft, nicht Vollständigkeit und nicht die Semantik von `kind`. Der Journal-Adapter nutzt die allgemeine Schicht `provenance_verification.md`: Coverage `all_selected_sources` rehydriert den kanonischen Volltext jeder ausgewählten Nutzerquelle lokal. Ein Claim `Morgens` entfernt weder Brot noch Anna noch Suppe. Modell-Labels sind unverifiziert und fehlen im Stufe-2-Artefakt. Stufe 2 erhält nur das lokal erzeugte `VerifiedArtifact` (`kind=verified_artifact`, `sources[].text`). Allgemeine Invarianten und Tests: `backend/provenance.py`, `backend/tests/test_provenance.py`. Journal-Adapter: `journal_reconstruct.py`. **Additiv 2026-08-26 (lokale Coverage-Autorität):** Stufe-1-JSON bleibt untrusted. Ist es unvollständig oder ungültig, wird es **nicht** still repariert und **nicht** an Stufe 2 weitergereicht. Stattdessen erzeugt der Journal-Adapter lokal ein `VerifiedArtifact` aus allen ausgewählten Nutzerquellen (volle Rehydration). Stufe 2 läuft damit weiter. Abbruch nur, wenn es gar keinen Nutzertext gibt (`no_user_sources`). Das ist keine Auto-Reparatur des Modell-JSON. **Additiv 2026-08-26 (Identitätsleak):** Response Validation bleibt fail-closed. Klartext-Identität in der Rohantwort wird nicht demaskiert und nicht als Entwurf übernommen. Nach einem Korrekturversuch erzeugt der Journal-Adapter lokal denselben Coverage-Pfad (Stufe 1) bzw. einen Entwurf aus dem lokalen Artefakt (Stufe 2). Homonyme (Speise vs. Personenname) gelten nicht als Leak. **Additiv 2026-08-26 (lokale Stufe 1, ein Narrations-Call):** Der Runtime-Pfad führt Stufe 1 nicht mehr als `mvp.journal_reconstruct` aus. Ablauf: 1. Tagesquellen lokal laden, Quellen-IDs vergeben, `local_verified_artifact` aus allen ausgewählten nichtleeren Nutzerquellen erzeugen. Assistant-Zeilen gehören nicht zum Artefakt. Reihenfolge bleibt die Begegnungsreihenfolge. Keine neue modellgestützte Semantik. 2. Stufe 2 (`mvp.journal_generate`) erhält nur Verified Artifact, Writing Profile und optional bestehenden Text. Das ist der einzige externe Generierungs-Call des normalen Laufs. `mvp.journal_reconstruct` bleibt als Prompt und Parser/Validator vorhanden, ist aber kein aktiver Modellpfad. Ungültiges ehemaliges Stufe-1-Modell-JSON ist deshalb im Runtime-Pfad irrelevant. Budget: kein separates Rekonstruktionsfenster; Artefakt plus Writing Profile plus optionaler Bestand müssen in das Stufe-2-Fenster passen. Übergröße bleibt `prompt_budget_exceeded` ohne stille Kürzung. Mindestfenster 32K und deaktivierte Context Compression bleiben. Detect prüft den gesamten Stufe-2-Egress (ggf. gechunkt). Nach erfolgreicher Detection: ein Narrations-Call, nur bei echtem aktivem Response-Leak maximal ein weiterer Narrationsversuch. **Additiv 2026-08-26 (semantische Detection):** Detect ist intent-neutral. Der Journal-Adapter wählt nur, welcher Klartext in den Generate-Egress kommt. Keine Journalbegriffe in Provenienz oder Privacy. Details: `privacy_gateway.md` §9.5. Das vollständige Writing Profile bleibt lokale Source of Truth. `compile_task_brief("journal_generate")` kompiliert nur den Aufgabenbrief (Core knapp, Facet-Delta `autobiographical_journal`, höchstens sechs relevante Traits, ein bis zwei Exemplare, Zielgröße ca. 3.000–4.000 Zeichen, feldweise an Wortgrenzen, keine Wortmitte). ## 16.2 Budgetmodell Vor jedem Journal-Egress: | Größe | Herkunft | |---|---| | Effektives Kontextfenster | OpenRouter Models-API `context_length` / `top_provider.context_length`, sonst Env `KANSHO_PROVIDER_CONTEXT_LENGTH` | | Maximale Ausgabelänge | `top_provider.max_completion_tokens`, sonst Env `KANSHO_PROVIDER_MAX_COMPLETION_TOKENS` | | Reservierte Ausgabe | `min(4096, unterstützte max_completion_tokens)` | | Sicherheitsmarge | 15 % des Fensters (`KANSHO_JOURNAL_SAFETY_MARGIN`) | | Input-Schätzung | konservativ 2 Zeichen/Token (`KANSHO_TOKEN_CHARS_PER_TOKEN`); kein Modell-Tokenizer, keine Scheingenauigkeit | Mindestfenster für Journalgenerierung: **32.768 Tokens**. Darunter kontrollierte Ablehnung, kein Modellwechsel. Katalog: `backend/model_catalog.py`, Cache TTL 3600 s (`KANSHO_MODEL_CATALOG_TTL_SECONDS`). Bei Fetch-Fehler gilt ein noch gültiger Stale-Cache; sonst Env-Fallback; sonst `model_metadata_unknown`. Fake-Provider: 32K/4096. Request nur, wenn geschätzter Input + reservierte Ausgabe in das geminderte Fenster passen. Tagesdialog: konfigurierbare Nachrichten-Sicherheitskappe `KANSHO_JOURNAL_DAY_MAX_MESSAGES` (Default 500) plus Tokenbudget. Overflow bricht ab; keine stillen Mittelkürzungen, keine Wortmitte. ## 16.3 OpenRouter Journal-Calls setzen `max_tokens` auf die reservierte Ausgabe und `plugins: [{"id":"context-compression","enabled":false}]`. Nur OpenRouter. Lokale und andere Provider erhalten diese Felder nicht. `provider.data_collection=deny` bleibt der bestehende ZDR-Pfad. ## 16.4 Fehlercodes | Code | Bedeutung | |---|---| | `model_context_too_small` | Effektives Fenster unter 32K | | `prompt_budget_exceeded` | Input plus Reserve plus Marge passt nicht; nichts wurde gekürzt | | `model_metadata_unknown` | Fenster nicht sicher bestimmbar | | `reconstruction_invalid` | Modell-JSON der Stufe 1 unvollständig oder Assistant als Tatsache (Validator; Generate bricht daran nicht ab) | | `no_user_sources` | Ausgewählte Gespräche enthalten keinen Nutzertext; Generate bricht ab | | `output_limit_unsupported` | Reservierte Ausgabelänge nicht tragfähig | | `provider_context_length_rejected` | Anbieter lehnte wegen Kontextlänge ab | Nutzertext plus `diagnostics` ohne Prompts, Antworten, Klarnamen, Mapping. ## 16.5 Diagnose Admin-Trace (`trace.budget`, `trace.stages`): verwendetes Modell, geschätzte Input-Tokens, tatsächliche `prompt_tokens` / `completion_tokens` / `total_tokens` / Kosten soweit geliefert, Fenster, Reserve, Marge, Budget-OK, Compression disabled/not_applicable, Abbruchgrund. Stufe 1 erscheint als `purpose=local_source_artifact` ohne Provider/Modell, Tokens/Kosten 0, Coverage `all_selected_sources`, Status lokal erfolgreich. Vollständige Prompts und Antworten nur im direkten Admin-Response des aktuellen Requests, nicht in einer globalen Historie oder Datenbank. Compact-Diagnose ohne Promptkörper darf prozessweit nur den letzten kompakten Status halten. `local_label`, Mapping-Tabelle, Klartextname und verworfene Rohantworten gehören nicht in persistente Diagnose. ## 16.6 Bewusst offen Chunk-and-Merge für übergroße Tage, Tokenizer je Modellfamilie, persistente Audit-Tabelle ohne Prompt-Inhalt, Quasi-Identifikatoren. **Additiv 2026-08-26 (Journalprosa):** `mvp.journal_generate` formuliert aus verifizierten Informationen eigenständige Journalprosa. Der Quellwortlaut ist keine Ausgabevorlage. `shape_journal` ersetzt akzeptierten Modelltext nicht durch Dialogzeilen. Der aktuelle Tagesdialog wird nicht als Stilprofil an denselben Lauf angehängt; ohne individuelles Writing Profile gilt ein neutraler Journalstil. Systemprompts mit unverändertem Template werden über `seed_revision` `2026-08-26-journal-narration-v1` idempotent aktualisiert; unabhängig editierte Prompts bleiben. **Additiv 2026-08-26 (Editorial Modes und wirksames Profil):** Journal-Generate wählt lokal `prose_edit` oder `notes_to_journal` (`journal_editorial.py`), ohne zweiten Modellaufruf. Der Narrationsprompt trennt `CURRENT_DAY_SOURCES`, `WRITING_PROFILE` und `STYLE_EXAMPLES`. Stufe 2 erhält den kompakten bestätigten Task Brief (Core, Journal-Facet, höchstens sechs Traits) plus höchstens zwei historische finale Einträge oder Importe als Stilreferenz; der aktuelle Kalendertag ist ausgeschlossen. Unbestätigte Profile sind keine Stilautorität (neutraler Fallback). Textähnlichkeit steht als Diagnose im Trace, löst keinen Retry aus und verwirft keinen Text. Identitätsprüfung gilt für Titel und Textkörper. Private Gateway-Hilfsfunktionen werden über öffentliche, intent-neutrale Namen genutzt (`canonical_token`, `is_identity_mention`, `identity_occurrence_count`, `identity_label_pattern`). **Additiv 2026-08-27 (Narrationsvertrag v2):** `seed_revision` `2026-08-27-journal-editorial-v2` macht den redaktionellen Auftrag explizit: unveränderlicher Inhalt versus erforderliche sprachliche Gestaltung, inklusive unvollständiger Sätze und Gewichtung belegter Kontraste. Der request-scoped Journal-Trace führt ohne Klartext: Prompt-Slug und Revision, Editorial Mode, Modelltext übernommen ja/nein, Antwort-Normalisierung, Provenienz, Writing-Profile-Präsenz (Core/Facet/Traits/Briefgröße), Stilquellen nach Typ und Größe, budgetentfernte optionale Blöcke, lexikalische Ähnlichkeit, unvollständige Syntax, Tokens, Kosten, Laufzeit. Compact-Diagnose bleibt intent-neutral (`prompt_revision`, `generate_ms`, `response_normalization`, `generate_calls`, `model_text_accepted`). Opt-in-Vergleich: `backend/journal_eval.py` (Baseline / vorheriger Vertrag / aktueller Prompt; `--profile-ab`). Nicht Teil des Produktionslaufs. Live nur mit `--live --profile-id` über das Privacy Gateway. Das Harness erklärt keinen Sieger. **Additiv 2026-08-27 (Privacy vor dem Socket, Provenienz danach):** Span-genaue Maskierung für request-lokale Detect-Treffer; bestätigte Registry bleibt separates Safety-Net. Response-Verarbeitung normalisiert aktiven Klartext lokal (`active_cleartext_normalized`) und startet keinen zweiten Generate-Call. Der Journal-Adapter prüft heutige Quellenbelege und speichert bei nicht lokal lösbarem Fehler keinen Draft (`journal_generation_not_accepted`). Editorial Mode klassifiziert über Bullet-/Fragment-/Satzstruktur. Detect-Eval bewertet exakte Spans; `openai/gpt-4.1-nano` bleibt konfiguriert und qualitativ unbestätigt. **Additiv 2026-08-27 (Generation Policy v3):** `seed_revision` `2026-08-27-journal-policy-v3` ersetzt den unveränderten System-Default durch einen kürzeren Prompt ohne synthetische Arbeitsbeispiele. Vier journalspezifische Parameter (`transformation_strength`, `detail_retention`, `voice_strength`, `narrative_shaping`) werden lokal in Stufenanweisungen kompiliert und über die Prompt Engine als `{{transformation_instructions}}` und Geschwisterplatzhalter aufgelöst. Das sind semantische Ausgabeeinstellungen, keine Modelltemperatur und keine modellspezifischen Promptvarianten. Unveränderte Defaults werden idempotent aktualisiert; unabhängig editierte Prompts bleiben. Request-scoped Trace: Werte, Stufennamen, Quelle `request`/`profile`, `remembered`. Keine persistente Speicherung der kompilierten Anweisungstexte. Tests: `backend/tests/test_journal_generation_policy.py`. **Additiv 2026-08-27 (administrierbare Anweisungsfragmente):** Die sprachlichen Stufentexte liegen nicht im Python- oder Frontend-Code. Tabelle `generation_instruction_fragments`, Seed `backend/config/generation_instructions.seed.json`, Governance wie `ai_prompts` (unangetastet aktualisieren, Admin-Edits behalten, Reset auf aktuellen Seed). Der Compiler wählt nur. Ungültige Laufzeitkonfiguration: `generation_policy_invalid`, kein Provideraufruf, kein Draft. Admin: `/admin/generation` und `/api/admin/generation-instructions/{purpose}`. Trace zusätzlich: `selection` (`variant_key`), `config_revision`, Editorial Mode. **Additiv 2026-08-27 (benannte Richtlinienausprägungen):** Numerische 0–100-Werte und Schieberegler sind im aktiven Pfad entfernt. Tabelle `generation_guidelines` trägt versionierbare Ausprägungen (`transformation`, `detail`, `voice`, `narrative`, plus automatische `source_mode`). Seed-Texte bleiben fachlich unverändert. Aktive Ausprägungen sind unveränderlich; Klon erzeugt einen Draft, Reset legt neue Seed-Drafts an und überschreibt keine historische Variante. Generate sendet `generation_selection` und optional `remember_generation_selection` (Default false). Unbekannte oder nicht aktive IDs: `invalid_generation_selection`. Leere Anweisung oder unaufgelöster Platzhalter: `generation_policy_invalid` / `unresolved_placeholder`, kein Provideraufruf, kein Draft. `journal_drafts.generation_snapshot` speichert IDs, Keys, Labels, Revisionen, Quellenmodus, Prompt-Slug/-Revision und Modell, nicht Promptkörper. Admin-UI: Karten je Dimension, Anweisungstext erst nach Öffnen. Tests: `backend/tests/test_journal_generation_policy.py`. **Additiv 2026-08-27 (Mischquellen, Promptvertrag ohne Source Mode):** Die exklusive Modusentscheidung `prose_edit`/`notes_to_journal` (`choose_editorial_mode`, `EDITORIAL_MODES`, `source_mode_instructions`) war eine technische Zwischenlösung und ist aus dem aktiven Pfad entfernt. Mischquellen sind der Normalfall und werden in einem Generate-Aufruf verarbeitet; keine dritte Kategorie. Persistierte `source_mode`-Zeilen werden beim Seed archiviert und erscheinen nicht in der normalen Adminoberfläche. Runtime-Promptvertrag von `mvp.journal_generate`: `transformation_instructions`, `detail_instructions`, `voice_instructions`, `narrative_instructions`, `writing_profile`, `style_examples`, `reconstruction`, `existing_text`. `seed_revision` `2026-08-27-journal-mixed-sources-v1`. Unveränderte Systemdefaults werden idempotent aktualisiert; unabhängig editierte Prompts nicht überschrieben. Legacy-Custom-Prompt mit veraltetem Platzhalter: `prompt_contract_incompatible`, kein Provideraufruf, kein Draft. Snapshot ohne `source_mode`/`editorial_mode`. Tests: `backend/tests/test_journal_generation_policy.py`. **Additiv 2026-08-28 (Textintegrität, Detect-Diagnose, Richtlinienauswahl):** `shape_journal` trimmt nur noch. Die frühere lokale Namens-/Pronomenheuristik nach Demaskierung ist aus dem akzeptierten Modellpfad entfernt; Wiederholung bleibt besser als ein falsch erratener Personenbezug. `paragraphize` gilt nur für `source=fallback`. Trace `reply` ist der gespeicherte Entwurf. Bestätigte Registry-Namen und Aliase werden unabhängig vom Detect-Modell im gesamten Egress maskiert; Detect-Treffer bleiben request-scoped. Diagnose behauptet keine semantische Vollständigkeit. Die Journal-UI sendet die gewählte Richtlinien-ID; Preview und Generate nutzen `compile_selection`. Ungültige Auswahl: `invalid_generation_selection` vor Detect/Generate. Katalog-Publish oder Default ändert eine gespeicherte Nutzerauswahl nicht. Wortlaut der Generierungsrichtlinien unverändert. **Additiv 2026-08-28 (sichtbare Modellantwort):** `_normalize_active_cleartext` bleibt die interne Prüffassung. `complete()` speichert `_visible_response`: Originalantwort, nur ausgegebene Platzhalter rehydriert. Modell-Klartext behält die Oberflächenform. **Additiv 2026-08-29 (Stilkontext der Stilanwendungsvariante):** `generation_guidelines.style_context_json` gehört zur Dimension `voice`. Seed `2026-08-29-voice-style-context-v1` legt die Ausprägungen `Ohne persönliches Profil`, `Persönliche Stimme dezent`, `Persönliche Stimme deutlich` und `Persönliche Stimme mit Beispielen` an und aktualisiert unangetastete Systemseeds; Klone und gespeicherte IDs bleiben. `compile_task_brief` filtert Core/Facet/Traits; Style Examples nur bei Freigabe. Leere WRITING_PROFILE-/STYLE_EXAMPLES-Blöcke entfallen. Prompt `mvp.journal_generate` `seed_revision` `2026-08-29-voice-style-context-v1`. Request-scoped Trace `style_application`: angeforderte vs. wirksame Quellen, Zeichen/Tokens je Block, Auslassungsgrund inkl. Budget. Admin bearbeitet Anweisung und Stilquellen gemeinsam. Generate-UI bleibt bei einer Auswahl „Persönliche Stimme“. Tests: `backend/tests/test_journal_style_context.py`. **Additiv 2026-08-29 (Legacy-Vergleich und immutable Seeds):** Seed `2026-08-29-voice-legacy-immutable-v1` fügt vier Vergleichs-IDs `journal-generate-voice-legacy-*` hinzu, ohne die aktuellen Voice-IDs, Defaults oder gespeicherten Auswahlen zu ändern. `seed_generation_instructions` überschreibt vorhandene IDs nicht mehr; abweichende Semantik erzeugt eine Nachfolge-ID (`cloned_from`, erhöhte `revision`). Prompt `mvp.journal_generate` lässt den Standing-Satz zu `STYLE_EXAMPLES` weg, wenn kein Beispielblock gesendet wird. Tests: `backend/tests/test_journal_style_legacy_immutable.py`. **Additiv 2026-08-29 (Detect-Vertragsretry und azyklische Fehlerdiagnose):** Ein ungültiger Detect-Vertrag verwirft den Pass und startet genau einen vollständigen Neuversuch mit allgemeiner Schemaanweisung. Der zweite Fehlschlag bleibt fail-closed vor dem Narrationsmodell. `trace.budget` ist ein Compact-Snapshot, kein Live-`diagnostics`. `sanitize` ist zyklensicher. Persistenzfehler ersetzen den `EngineError` nicht. Tests: `backend/tests/test_detect_contract_retry.py`. Tests: `backend/tests/test_journal_budget.py`, `backend/tests/test_journal_narration.py`, `backend/tests/test_journal_editorial.py`, `backend/tests/test_journal_eval.py`, `backend/tests/test_journal_shape.py`, `backend/tests/test_privacy_response_integrity.py`. Allgemeine Provenienzschicht: `provenance_verification.md`, `backend/tests/test_provenance.py`. --- # 10. Querverweise - Fit-Gap: `../functional/mvp_stand_und_abgleich.md` - API-Liste: `backend_and_api.md` §5.1 - Tabellen: `memory_storage_and_offline.md` §6 - Egress: `privacy_gateway.md` §9 - Journal-Budget und zweistufige Generierung: §16 - Routen: `frontend_pwa_shell.md` §6.1 - Stack-Ziel: `product_frame_and_stack.md` - Ports: `runtime_and_deploy.md`