445 lines
32 KiB
Markdown
445 lines
32 KiB
Markdown
---
|
||
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/<profile_id>/` |
|
||
| 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: Stufe 1 Rekonstruktion, Stufe 2 Narration |
|
||
| `journal_reconstruct.py` | Lokale Validierung der Stufe-1-JSON |
|
||
| `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 |
|
||
| `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`.
|
||
|
||
**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 `` |
|
||
| `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. Zwei Gateway-Zwecke nacheinander: `journal_reconstruct`, dann `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` | 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.
|
||
|
||
**Additiv 2026-08-25:** Der Journalentwurf ist fachlich zwei Stufen (Rekonstruktion, dann Narration), also zwei Generate-Calls hinter derselben expliziten Generate-Aktion. Dialogzug bleibt ein Call. Details: §16.
|
||
|
||
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: Day-Messages mit Budget. Generate-Stufe 2: Writing Profile plus validierte Rekonstruktion, 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.
|
||
|
||
---
|
||
|
||
# 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` |
|
||
| Journal-Budget / zweistufige Generierung | `backend/tests/test_journal_budget.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 Abnahmelauf | `scripts/test-mvp.ps1` |
|
||
|
||
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, 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` fällt auf Muster zurück). Lokales Detect bleibt austauschbare Rolle.
|
||
- 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 Testeinstieg `scripts/test-mvp.ps1` (UTF-8, isolierte Temp-Daten, keine Live-Keys; Fake nur in den Suites, die ihn setzen).
|
||
- 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}}`.
|
||
- 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.
|
||
|
||
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
|
||
|
||
Explizites Generate durchläuft 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.
|
||
|
||
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. 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.
|
||
|
||
## 16.6 Bewusst offen
|
||
|
||
Chunk-and-Merge für übergroße Tage, Tokenizer je Modellfamilie, persistente Audit-Tabelle ohne Prompt-Inhalt, Quasi-Identifikatoren.
|
||
|
||
Tests: `backend/tests/test_journal_budget.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`
|