Commit graph

224 commits

Author SHA1 Message Date
Jens Reinemann
7ea7729f96 fix: version display format -> three-number (e.g. 1.7.10) without v prefix 2026-05-18 12:16:01 +02:00
Jens Reinemann
461fca7ead chore: release v1.7 (10) 2026-05-18 12:09:11 +02:00
Jens Reinemann
39956cc7d9 feat(publish): Python-basierter publish-apk Workflow 2026-05-18 12:08:12 +02:00
Jens Reinemann
09e01dff00 style: Beton & Stahl Theme - höhere Kontraste, stahlblaue Surfaces 2026-05-18 12:08:06 +02:00
Jens Reinemann
8459705bb1 chore: release v1.7 (9) 2026-05-18 11:46:28 +02:00
Jens Reinemann
23e0a47967 fix(publish): robustes Error-Handling in publish-apk.ps1
- Pre-Checks vor jeder Dateiänderung (Token, Regex-Validierung, SSH-Agent)
- Rollback: build.gradle.kts wird bei Build- oder Upload-Fehler zurückgesetzt
- SCP/SSH mit ConnectTimeout (kein ewiges Hängen bei VPS-Ausfall)
- API-Call mit Retry (2 Versuche, 3s Pause) + Recovery-Hinweis
- Verify + Git: non-fatal (nur Warnung, kein Abbruch)
- versionCode-Validierung: neuer Code muss > aktueller sein
- Set-StrictMode -Version Latest
2026-05-18 11:38:01 +02:00
Jens Reinemann
f3eab7b10d chore: deploy.ps1 entfernt (Logik in publish-apk.ps1) 2026-05-18 11:34:51 +02:00
Jens Reinemann
76ad50e3aa refactor(publish): publish-apk.ps1 übernimmt vollständigen Deploy-Workflow
- publish-apk.ps1: Version-Bump, Build, SCP, API, Verify, git commit/push
  (alle Parameter optional – Standalone-Nutzung ohne Copilot möglich)
- publish.prompt.md: vereinfacht (kein manueller Version-Bump mehr nötig)
- SKILL.md: Parameter-Tabelle aktualisiert
- deploy.ps1 im Root entfernt (Logik lebt jetzt im Skill)
2026-05-18 11:34:42 +02:00
Jens Reinemann
301d60aea4 chore: lokales deploy.ps1 statt GitHub Actions
- deploy.ps1: ein Befehl für vollständigen Release-Workflow
  (version bump + build + SCP + API + git commit/push)
- android-ci.yml entfernt (kein GitHub-Quota-Verbrauch mehr)
- BOLLWERK_ADMIN_TOKEN muss als Env-Var gesetzt sein
2026-05-18 11:29:47 +02:00
Jens Reinemann
ac5f346858 chore: .kotlin/ zu .gitignore hinzufügen 2026-05-18 11:25:14 +02:00
Jens Reinemann
ca1680b3a2 fix(genome): Distillation-Prompt schärfen – Specialized-Definition + Anti-Patterns
- Specialized klar abgegrenzt: nur konkrete Server-Deployments, Renames, Seeding
- 5 häufige Distillation-Fehler dokumentiert (member-added ignorieren, nur Anfang lesen, etc.)
- Erweiterte Evolutions-Muster (GPU-Hardening, Hot-Reload, API statt Restart)
- Zusätzliche Sanitization-Regeln (Server-IPs, Package-Namen)
- Erwartete Conversion-Rate: 15-30% als Orientierung
2026-05-18 11:23:56 +02:00
Jens Reinemann
bb578c5076 chore: version bump 1.6 (7) -> 1.7 (8), CI Deploy-Workflow redesign
- Version 1.7 (versionCode 8) deployed auf bollwerk.online
- android-ci.yml: nur workflow_dispatch, kein auto-Trigger
- Auto-Versionierung: versionCode wird im CI hochgezählt
- CI deployed direkt auf VPS via SCP + API-Call
- VPS: BOLLWERK_ADMIN_TOKEN konfiguriert (einmalig)
Benötigte Secrets in GitHub: BOLLWERK_ADMIN_TOKEN, VPS_SSH_PRIVATE_KEY
2026-05-18 11:09:26 +02:00
Jens Reinemann
5e73eccce6 chore(genome): alle genome-prompts auf Claude Opus 4.6 umgestellt 2026-05-18 10:55:53 +02:00
Jens Reinemann
ea3bd6dc97 fix(sync): robuste WebSocket-Verbindung und Token-Refresh
- Backoff nur nach stabiler Verbindung (>30s) zurücksetzen
  → verhindert rapiden 2s-4s-2s-Reconnect-Oscillation
- VIOLATED_POLICY Close-Reason erkennen → AuthRejected-Event
  → kein endloser Retry mit abgelaufenem Token
- Token-Refresh bei AuthRejected: MainViewModel refresht Access-Token
  und reconnectet WS automatisch; bei Fehlschlag Session-Expired
- executeItemRequest: fehlende 401-Retry-Logik ergänzt (Bug 4)
- SyncService.refreshAccessToken() als neue Interface-Methode
2026-05-18 10:48:46 +02:00
Jens Reinemann
e52f041d31 feat(genome): agent fragt immer nach Quell-Repo, Zeitraum und Ziel-Repo 2026-05-18 10:44:41 +02:00
Jens Reinemann
7dfdb6e505 chore(genome): formatting fixes (whitespace/table alignment) 2026-05-18 10:17:43 +02:00
Jens Reinemann
2071d758b5 fix(genome): improve distillation precision + propagation safety
genome-distill.prompt.md:
- Warnung vor versteckten Evolutions in Multi-Change-Commits
- Anleitung: Commit-Message ignorieren, jeden Hunk einzeln prüfen
- Rename-Churn-Erkennung (A→B→A = Specialized)
- Häufige versteckte Evolutions-Muster dokumentiert
- Analyse-Bericht am Ende mit Churn-Counter

genome-propagate.prompt.md:
- Neue Traits: immer einzeln vom User bestätigen lassen
- Nie automatisch neue Traits anlegen ohne explizite Bestätigung
2026-05-18 10:14:54 +02:00
Jens Reinemann
887cdbd3f7 feat(settings): server-sync UI aufräumen (#108)
- Login-Status + Logout auf eigene ElevatedCard
- Logout-Button als OutlinedButton (Material-Button statt TextLink)
- Letzte Sync direkt unter Verbindungsstatus ohne Divider
- Refresh-IconButton neben Serverstatus entfernt
- Server-URL Reset nur sichtbar wenn nicht-default Adresse
- Manuelle Sync-Buttons entfernt (vollautomatisch)
2026-05-18 10:09:58 +02:00
Jens Reinemann
bdd8cb4b11 feat(#106): category tap on dashboard navigates to inventory with filter
- Change Screen.ItemList from data object to data class with optional categoryId
- DashboardScreen: make CategoryCard clickable with onCategoryClick callback
- ItemListViewModel: read initial categoryId from SavedStateHandle
- BollwerkNavGraph: wire category click to navigate with categoryId
- Add test for initial category filter from navigation args
2026-05-18 10:01:14 +02:00
Jens Reinemann
7ccd2dc1fd refactor(genome): restructure as skill + prompt verbund
- Skill: .github/skills/genome/ (SKILL.md + genome-extract.py)
- Router: .github/prompts/genome.prompt.md (orchestriert alle 3 Phasen)
- Sub-Prompts: genome-distill.prompt.md, genome-propagate.prompt.md
- Output: .github/genome/output/ (gitignored)

Aufruf: /genome → fragt Quell-Repo + Zeitspanne, führt
Extraction → Distillation → Propagation durch.
2026-05-18 09:59:59 +02:00
Jens Reinemann
9cc69678e7 feat(genome): Phase 3 - Propagation Prompt
Matched Growth Vectors auf Ziel-Genome, generiert konkrete Patches
als Checkliste (Critical=an, Evolution≥7=an, <7=aus).
User wählt aus, Agent wendet Patches an.
2026-05-18 09:52:50 +02:00
Jens Reinemann
6318b0efe5 feat(genome): Phase 2 - Distillation Prompt
Klassifiziert Mutations (Critical/Evolution/Specialized),
scored Übertragungswert (1-10), sanitized sensitive Daten,
filtert projektspezifische Änderungen heraus.
2026-05-18 09:51:49 +02:00
Jens Reinemann
8e7352dcc4 feat(security): replace CleartextKeysetHandle with AndroidKeysetManager (#105)
- Extract PrivateKeysetStore interface for testability
- Add AndroidKeystorePrivateKeysetStore (Android Keystore-backed AEAD)
- Refactor E2EEKeyManager to use PrivateKeysetStore
- Add legacy migration: old cleartext key is removed, forcing re-generation
- Update DI module to provide AndroidKeystorePrivateKeysetStore
- Adapt unit tests with FakePrivateKeysetStore + migration test

Private key material no longer appears as cleartext JSON on the JVM heap.
Existing devices with legacy keys will re-generate and re-upload via
EnsureKeyPairUseCase on next app launch.
2026-05-18 09:51:24 +02:00
Jens Reinemann
10cb474906 refactor(genome): rewrite extraction in Python for proper UTF-8 support
PowerShell auf Windows hat Encoding-Probleme mit Git-Output (Umlaute).
Python 3 handhabt UTF-8 nativ korrekt.
2026-05-18 09:49:37 +02:00
Jens Reinemann
24c6fac0f8 feat(messaging): push notifications for incoming messages (#104)
- Add NotificationHelper with channel creation, grouped notifications,
  and deep-link PendingIntent into chat
- Trigger notification from MessageRepositoryImpl on WebSocket NewMessage
- Active-chat suppression in ChatViewModel (no notification for current chat)
- Deep-link from notification tap: MainActivity handles intent extras,
  MainScreen navigates to correct Chat screen
- Add POST_NOTIFICATIONS permission to AndroidManifest
- Add notification icon drawable (ic_notification_message)
- Add unit tests for notification suppression logic
- Fix pre-existing test compilation (SyncServiceImplTest missing authEventBus)
2026-05-18 09:39:39 +02:00
Jens Reinemann
8e75798507 feat(genome): Phase 1 - Extraction Script
Implementiert genome-extract.ps1:
- Trait-Erkennung (Skills/Agents/Prompts/Instructions)
- Verbund-Erkennung für Prompt-Router + Sub-Prompts
- Git-Log-Scanning mit Zeitspanne
- Mutation-Typ-Klassifizierung (content-change/member-added/member-removed)
- Strukturierte Markdown-Ausgabe mit Diffs
2026-05-18 09:32:57 +02:00
Jens Reinemann
5a26d6a85e refactor: rename workflow-*.prompt.md → nextstep-*.prompt.md
Namenskonvention für Genome Engine Verbund-Erkennung:
Router <name>.prompt.md + Sub-Prompts <name>-*.prompt.md
ermöglicht rein pfadbasierte Trait-Zuordnung ohne Content-Parsing.
2026-05-18 09:26:36 +02:00
Jens Reinemann
c771aa9547 feat(messaging): enforce 10 MB mailbox limit per receiver with FIFO eviction (#103)
- Add getUndeliveredStorageBytes() and evictOldestUndelivered() to MessageRepository
- Check mailbox size before saving; evict oldest undelivered messages if over 10 MB
- Return systemMessage in SendMessageResponse when eviction occurs
- App parses systemMessage and displays it in the sender's conversation
- Add SendMessageResponse to shared module for server/app interop
- Update existing tests to use new response format
- Add 3 new tests for eviction behavior
2026-05-18 09:17:15 +02:00
Jens Reinemann
6a8ffa17be feat(messaging): remove non-functional emoji button (#102) 2026-05-18 08:45:34 +02:00
Jens Reinemann
01a6d911ec feat(server): add POST /api/admin/version endpoint for APK deploy without restart
- Add VersionStore that persists versionCode/versionName to data/version.json
- Add POST /api/admin/version secured by BOLLWERK_ADMIN_TOKEN bearer auth
- GET /api/version now reads from VersionStore (fallback to env-vars)
- Update publish-apk.ps1 to use API call instead of SSH+sed+restart
- Update publish SKILL.md and vps-deploy SKILL.md documentation

Closes #100
2026-05-18 08:40:31 +02:00
Jens Reinemann
dad15b9e94 security: WebSocket Auth-Token aus Query-Parameter in Authorization-Header verschieben
- Client: Token als 'Authorization: Bearer' Header statt ?token= Query-Parameter senden
- Server: Token aus Authorization-Header statt Query-Parameter lesen
- Tests: Alle 8 WebSocket-Tests auf Header-Auth umgestellt
- Integration-Tests: WebSocket-Verbindung mit Header aktualisiert

Closes #97
2026-05-18 08:23:10 +02:00
Jens Reinemann
75f46de05e feat(server): seed test users alice and bob on startup 2026-05-18 08:19:58 +02:00
Jens Reinemann
dad2907481 feat: WebSocket-Lifecycle und Sync ab App-Start unabhaengig von Settings-Screen
- MainViewModel: verbindet WebSocket beim App-Start (connectOnStartup) und
  nach Login (via AuthEventBus.loginSuccess). Behandelt alle WebSocket-Events
  (Connected/FullSyncRequired/InventoryUpdated) -> pullSync/pushSync.
  Auto-pushSync wenn Server leer ist und lokale Daten vorhanden (Daten-Recovery).
- AuthEventBus: loginSuccess-Signal ergaenzt (serverUrl + token)
- SyncServiceImpl: emittiert loginSuccess nach erfolgreichem Login
- SettingsViewModel: WebSocket-Lifecycle entfernt (nur noch ConnectionFailed
  fuer UI-Fehlermeldung). Manueller Sync-Button bleibt erhalten.
- WebSocketClientImpl: vollstaendiges Logging, wiederholende User-Benachrichtigung
  bei Verbindungsfehlern (alle MAX_RETRIES Versuche statt nur einmalig)
2026-05-18 01:17:47 +02:00
Jens Reinemann
575c0ad709 feat: automatic forced logout on expired session
When both access and refresh token are invalid (401 on /auth/refresh),
the app now automatically logs out and navigates to Settings (login form).
No data loss - only auth tokens are cleared, local inventory data is intact.

- AuthEventBus: singleton SharedFlow that signals session expiry
- MainViewModel: observes bus, calls logout + disconnect, navigates to Settings
- MainScreen: LaunchedEffect collects navigateToSettings event
- MessageRepositoryImpl: emits session expired when refresh fails
- SyncServiceImpl: emits session expired when refresh fails
2026-05-18 00:55:25 +02:00
Jens Reinemann
a14c40d756 fix: 401 token refresh in MessageRepositoryImpl
fetchUsers() and attemptSendToServer() had no retry logic on 401.
SyncServiceImpl already had this pattern (auto-refresh on 401).

Add private refreshAccessToken(serverUrl) and retry once on 401
in both methods.
2026-05-18 00:44:58 +02:00
Jens Reinemann
ea02029dbe fix: SettingsKey circular init crash on app start
SENSITIVE_KEYS and SENSITIVE_KEY_STRINGS used eager initialization in the
companion object. When E2EEKeyManager.hasKeyPair() was the first access to
SettingsKey, it triggered SettingsKey.<clinit> which tried to resolve
StringKey.E2EEPrivateKeyset - but that class was already 'in initialization
by the current thread' (JVM spec). The JVM returned null, causing NPE in
SENSITIVE_KEY_STRINGS.map { it.key }.

Fix: use by lazy for both properties to defer initialization past <clinit>.
2026-05-18 00:39:32 +02:00
Jens Reinemann
d02a38455b chore: version bump 1.5 (6) -> 1.6 (7) 2026-05-18 00:32:53 +02:00
Jens Reinemann
09777238c9 docs: krisenvorrat -> bollwerk in allen Skill- und Doku-Dateien
/opt/krisenvorrat/ ist veraltet. Korrekt: /opt/bollwerk/
Regel jetzt auch in copilot-instructions.md festgehalten.
2026-05-18 00:28:40 +02:00
Jens Reinemann
8c0db56223 feat: E2EE Messaging mit Tink HPKE (X25519 + ChaCha20-Poly1305)
Closes #96

## App

- E2EEKeyManager: Tink HPKE Schlüsselpaar generieren, privaten Key
  via EncryptedSharedPreferences sichern, Nachrichten verschlüsseln
  und entschlüsseln (X25519 + ChaCha20-Poly1305)
- EnsureKeyPairUseCase: Keypair-Initialisierung beim App-Start;
  Public Key via HTTP PUT an Server übermitteln
- MainActivity: EnsureKeyPairUseCase.execute() in onCreate
- SettingsKey: E2EEPrivateKeyset + E2EEPublicKeyBase64 als SENSITIVE_KEYS
- MessageRepositoryImpl: sendMessage verschlüsselt Body mit Empfänger-
  Public-Key; eingehende Nachrichten werden lokal entschlüsselt und
  als Klartext in Room gespeichert; Public-Key-Cache (in-memory) +
  key_updated Handler
- WebSocketClient: KeyUpdated Event hinzugefügt
- WebSocketClientImpl: key_updated Frame parsen; Exception-Logging
- Tink 1.15.0 als neue Dependency

## Server

- V4 Flyway Migration: ALTER TABLE users ADD COLUMN public_key TEXT
- Tables.kt: publicKey Feld in Users-Objekt
- UserRepository: getPublicKey() / setPublicKey()
- UserRoutes: PUT /api/users/{id}/public-key (Auth + Owner-Check +
  Längenvalidierung ≤ 10.000 Zeichen) und
  GET /api/users/{id}/public-key
- WebSocketManager: notifyKeyUpdated() Broadcast an alle anderen
  verbundenen Clients
- MessageRepository: EncryptionService für message body bypassed –
  Server speichert E2EE-Ciphertext direkt (Zero-Knowledge)

## Tests

- E2EEKeyManagerTest: 5 Tests (Roundtrip, Nonce-Uniqueness,
  Wrong-Key, hasKeyPair)
- EnsureKeyPairUseCaseTest: 4 Tests (generate+upload, skip wenn
  vorhanden, kein Upload ohne UserId, kein Crash bei Server-Fehler)
- MessageRepositoryImplTest: 5 neue E2EE-Tests

## Docs

- docs/migration-guide.md: E2EE-Einschränkungen dokumentiert
  (Pending-Message Klartext in SQLite)

## Follow-up

- #105: E2EE Private Key – AndroidKeysetManager statt
  CleartextKeysetHandle (Security Hardening)
2026-05-18 00:22:28 +02:00
Jens Reinemann
bed233521e feat: Krisenvorrats-Inventar digitalisiert und Import-Skript erstellt
- inventar.md: 38 Artikel aus handschriftlicher Liste erfasst (Lebensmittel,
  Medikamente, Ausrüstung, Hygiene, Energie)
- import-inventar.py: Python-Skript zum einmaligen PUT /api/inventory
  (UTF-8-safe via ensure_ascii=True, kein PowerShell-Encoding-Problem)
- import-inventar.ps1: PowerShell-Variante (Umlaute via [char]-Variablen)
2026-05-17 23:21:07 +02:00
Jens Reinemann
9631ec9a92 chore: ungestagede Aenderungen und neue Docs committen 2026-05-17 22:51:07 +02:00
Jens Reinemann
d00b5b245a chore: version bump 5 -> 6 (1.4 -> 1.5) 2026-05-17 22:47:13 +02:00
Jens Reinemann
00e3f88980 fix(tests): UTF-8 in Invoke-Api korrekt escapen fuer PS5.1 Kompatibilitaet
PS5.1 kodiert String-Bodies in Invoke-RestMethod mit der Windows-Systemkodierung
(Windows-1252) statt UTF-8. Fix: Non-ASCII-Zeichen werden vor dem Senden als
\uXXXX escaped, sodass der HTTP-Body reines ASCII ist und korrekt interpretiert
wird. Schliesst die UTF-8-Testfaelle (Szenario 6a) vollstaendig ab: 35/35 PASS.
2026-05-17 22:33:45 +02:00
Jens Reinemann
90cfac70a0 feat: column-level encryption at rest with AES-256-GCM (#98)
- Add EncryptionService (AES-256-GCM) with passthrough when no key set
- Flyway V3: enable pgcrypto extension + widen name columns to TEXT
- DatabaseFactory: init EncryptionService from BOLLWERK_DB_ENCRYPTION_KEY,
  run migrateEncryptData() to encrypt existing plaintext rows on startup
- InventoryRepository: encrypt on write, decrypt on read for
  items.name, items.notes, categories.name, locations.name, settings.value
- MessageRepository: encrypt body on write, decrypt on read
- docker-compose.yml: document BOLLWERK_DB_ENCRYPTION_KEY env var
- docker-compose-vps.yml: pass BOLLWERK_DB_ENCRYPTION_KEY from .env
- .env.example: add key generation template
- .gitignore: add .env to ignore list

Closes #98
2026-05-17 22:17:10 +02:00
Jens Reinemann
045a4b7674 feat: Migration-Safety – Room v7, AutoMigration, Flyway, kein fallbackToDestructiveMigration (#99)
- fallbackToDestructiveMigration() aus DatabaseModule entfernt
- BollwerkDatabase auf Version 7 gebumpt
- AutoMigration(from=5, to=6) und (from=6, to=7) definiert
- MigrationTestHelper-Test migrate6To7_preservesData implementiert
- 7.json Schema-Export generiert
- Server: Flyway 9.22.3 integriert (baselineOnMigrate=true)
- V1__initial_schema.sql + V2__cleanup_user_id.sql angelegt
- Skill android-db-migration erstellt
- versionCode 5 / versionName 1.4
2026-05-17 21:17:24 +02:00
Jens Reinemann
3d7c01cef5 feat(update): AlertDialog bei verfuegbarem Update anzeigen
- AlertDialog in MainScreen zeigt verfuegbare Version mit Bestaetigung
- UpdateBanner blendet bei UpdateStatus.Available aus (Dialog uebernimmt)
- FEATURE_CAMERA_ENABLED temporaer deaktiviert

fix(server): Logo-Pfad und statische Ressourcen bereinigen

- /res-Route fuer classpath-Assets (logo.png etc.) hinzugefuegt
- Logo-Pfad von /static/logo.png auf /res/logo.png korrigiert
- Build-Nummer aus Versionsanzeige auf der Homepage entfernt

ci: GitHub Actions Workflow fuer Swift-Tests hinzugefuegt

style: Tabellenformatierung im Code-Reviewer-Agenten bereinigt
2026-05-17 20:52:47 +02:00
Jens Reinemann
9ff21cbc4b ui: Server-Synchronisierung als ElevatedCard neu gestaltet
- Status-Dot + Verbindungstext links, Sync-Button (Refresh-Icon) rechts
  in einer kompakten Zeile statt verstreuter Einzelelemente
- Bei Disconnect: Haupttext 'Keine Verbindung', Countdown als
  zweite Zeile darunter (statt alles in einer langen Zeile)
- Sync-Spinner ersetzt den Button solange Sync läuft
- Aktivitätsmeldung animiert unter der Statuszeile
- Letzte-Sync-Zeit mit Divider am Card-Boden, klar abgetrennt
- 'Synchronisierung erfolgt automatisch.' entfernt (redundant)
2026-05-17 20:35:15 +02:00
Jens Reinemann
fdc016c786 fix: Differenzierte Sync-Aktivitätsmeldungen
- Neues SyncActivityMessage.CheckingForChanges 'Prüfe auf Änderungen…'
  wird beim initialen Catch-up nach Connect angezeigt (statt dem
  irreführenden 'Empfange Inventar-Update…')
- Neues SyncActivityMessage.NoChanges 'Keine Änderungen'
  wird angezeigt wenn der initiale Sync keine Daten zurückliefert
- Echter Server-Push (InventoryUpdated/FullSyncRequired) zeigt weiterhin
  'Empfange Inventar-Update…' → jede echte Kommunikation bleibt sichtbar
2026-05-17 20:27:49 +02:00
Jens Reinemann
aafb9ddd64 fix: Verbindungs-Noise unterdrücken
- loadSettings() ruft connect() nur noch auf wenn ConnectionState
  NotConfigured oder Disconnected ist – verhindert den self-triggering
  Loop (pullSync → loadSettings → connect → Connecting → Connected → …)
- pullSync() erhält Parameter silent=true für den initialen Connect-Sync;
  zeigt kein 'Empfange Inventar-Update…' mehr beim bloßen Verbindungsaufbau
- Version 1.2 (3) → 1.3 (4)
2026-05-17 19:05:37 +02:00
Jens Reinemann
152f484d4f chore: remove Swift/iOS remnants
- Delete .github/workflows/ci.yml (Swift CI on macOS)
- Remove 'Swift Code Reviewer' agent from code-reviewer.agent.md
- Remove 'migration' label (C# → Swift) from gh-tickets SKILL
2026-05-17 19:00:31 +02:00