--- 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:** `product_frame_and_stack.md` nennt PostgreSQL. Der Slice läuft auf SQLite. Lokal zulässig; Prod-Pfad offen. Siehe Fit-Gap. 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-Call | | `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 | | `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 | | `db.py` + `schema.sql` | SQLite, Migrationen | Prompts in der DB, nicht im Anwendungscode: `mvp.dialogue_turn`, `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`. **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. **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 | | `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. Gateway `purpose=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, die UI setzt es nicht. 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` | Stub, Link zum Journal | | `/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` | Muster immer. Externes Klartext-Detect nur Development/Test (`KANSHO_ENV`). Produktiv: Muster oder lokale Detect-URL. | | 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. Interner Context-Builder (Klartext, lokal): fordert Selection-Specs an (`day_messages`, `space_entries`, `space_recent_sources`, `writing_profile`). Recency-Kappen (Day 80 Messages; 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 ohne Prior-Entries, mit Writing Profile. `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. --- # 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` | | Shape / Namen | `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` | | 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` | Tests beweisen API-Verträge und Invarianten, nicht Dialogqualität. --- # 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 | Muster immer; externes Klartext-Detect nur Development/Test; Produktiv fällt auf Muster zurück. Lokales HTTP-Detect bleibt Provider-Rolle. | 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, Papierkorb-UI, lokales Detect-Modell, Obsidian/mindnet/Kairo-Handoff. --- # 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` fällt auf Muster zurück). Lokales Detect bleibt austauschbare Rolle. - Response Validation blockiert Klartext-Identität vor Demask. - 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 - Stub-Home / Contextual Continuation als Start-IA --- # 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. Das statische Facet-Raster aus §12 ist **nicht** die Profilstruktur. Semantische Traits und der Initial Profile Build: §14. --- # 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`. --- # 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 - Routen: `frontend_pwa_shell.md` §6.1 - Stack-Ziel: `product_frame_and_stack.md` - Ports: `runtime_and_deploy.md`