128 lines
7.7 KiB
Markdown
128 lines
7.7 KiB
Markdown
# Changelog
|
||
|
||
Alle nennenswerten Änderungen an `ManaEventSync`. Semver strikt.
|
||
|
||
## [Unreleased]
|
||
|
||
## [0.10.0] — 2026-06-23
|
||
|
||
### Added
|
||
- **Cloud-Sync-Opt-in-Gate (DSGVO Art. 25), Parität zur Web-App.** Neues `EventSyncConfig.requireSyncConsent` (Default `false` → abwärtskompatibel, kein Verhaltenswechsel für nicht-adoptierende Apps). Ist es gesetzt, synct die Engine nur mit expliziter Nutzer-Einwilligung mit dem Server: ohne Zustimmung läuft sie lokal-only (anonym, Events stauen in der Outbox), auch bei vorhandenem Login — `authMode()` liefert dann `.anonymous`. Die Einwilligung wird beim `start()` aus mana-auth `GET /api/v1/settings` gelesen (App-Override vor globaler Präferenz, fehlend = aus; gleiche Quelle wie Web). Neu: `EventSyncEngine.setSyncConsent(_:)` (Konto-Toggle: `true` claimt anonyme Events + startet Sync wie `signIn`, `false` stoppt den Server-Sync, lokale Daten bleiben), `syncConsentRequired`, `hasSyncConsent`. Schließt die Inkonsistenz, dass Native bisher per Default synct, Web aber Opt-in ist.
|
||
|
||
## [0.9.0] — 2026-06-17
|
||
|
||
### Added
|
||
- **402-Handling (kein aktives Cloud-Sync-Abo).** `EventSyncError.subscriptionRequired` (Server `402`), `EventSyncEngine.onSyncRequired`-Hook + `isSyncRequired`. Push/Pull behandeln 402 sauber: Events BLEIBEN in der Outbox (kein Verlust, KEIN Attempt-Increment), event-getriggerte Push/Pull werden unterdrückt, der Poll prüft weiter (force) → Auto-Recovery, sobald das Abo aktiv ist. Wire-Parität zu `@mana/event-sync` 0.9.0. Siehe `../mana/docs/SYNC_SETTINGS.md`.
|
||
|
||
## [0.8.0] — 2026-06-16
|
||
|
||
### Added
|
||
- `EventSyncEngine.purgeAggregatePrefix(_:)` — löscht lokale Events per aggregateId-Prefix (A3: lokale Klartext-Residue eines gelöschten Mandanten wegräumen).
|
||
|
||
## [0.7.0] — 2026-06-16
|
||
|
||
### Added
|
||
- `EventSyncEngine.shredScope(scopeId:shredAfter:)` — App-API zum Crypto-Shredding eines Scope-Sub-Keys (Mandant löschen). Ohne `shredAfter` sofort (privat/Art. 17), mit → geplant (Firma/GeBüV-Frist).
|
||
|
||
## [0.6.0] — 2026-06-16
|
||
|
||
### Added
|
||
- **Per-Mandant-Crypto-Shredding (A3), Parität zu `@mana/event-sync` 0.7.0:**
|
||
`CryptoContext` + optionale context-Varianten von `encryptPayload`/
|
||
`decryptPayload` (Default-Extension forwarded → bestehende Provider
|
||
unverändert). `ScopeVaultClient` (getOrMint/get/shred gegen
|
||
`/encryption-vault/scopes/:appId/:scopeId/key`, 404/410). `ScopedCryptoProvider`
|
||
(Actor: scopeResolver, Sub-Key-Cache, Master-Fallback; geschreddert → wirft).
|
||
`createScopedKeyProviderFromVault`. `EventSyncConfig.scopeResolver` → Engine
|
||
baut den ScopedCryptoProvider statt nur Master; Context an emit/pull/reencrypt
|
||
durchgereicht. AES-GCM-seal/open in geteilte Funktionen extrahiert (Wire
|
||
identisch `enc:1:`). 31 Tests grün, abwärtskompatibel.
|
||
|
||
## [0.5.0] — 2026-06-10
|
||
|
||
Erster getaggter Release (`v0.5.0`) — ab hier Semver-Tags pro Release,
|
||
kanonische Version in `mana/docs/swift-libs.json`.
|
||
|
||
### Added
|
||
- **`EventSyncEngine.modelContainer` (public)** — Zugriff auf den SwiftData-
|
||
Container für Apps, die via `EventSyncConfig.additionalModels` eigene
|
||
`@Model`-Typen im selben Store ablegen (z.B. nutriphis `LocalBMRProfile`).
|
||
- **Decrypt-Failures sichtbar statt Silent-Skip** (Parität zu
|
||
`@mana/event-sync` 0.6.0): Ein nicht-entschlüsselbares Event aus dem Pull
|
||
wurde bisher nur geloggt und verschwand — die App sah nie, dass ihr Daten
|
||
fehlen (der memoro/nutriphi-`enc:1`-Footgun). Jetzt:
|
||
- persistenter Zähler in Meta (`decrypt-failure-count`, `…-last-at`,
|
||
`…-last-event`), abfragbar via `stats()` → `decryptFailures` /
|
||
`decryptFailureLastAt`, Reset via `clearDecryptFailures()`;
|
||
- neuer Hook `onDecryptFailure: (DecryptFailure) -> Void` pro Event,
|
||
zusätzlich feuert `onError` mit `EventSyncError.decryptFailed(eventId:)`;
|
||
- `decryptPulledPayload(_:eventId:crypto:)` wirft auch, wenn der Provider
|
||
den Ciphertext unangetastet durchreicht (NoOp ohne Key) — vorher wäre der
|
||
`enc:1:`-String als „Plaintext" in der Projektion gelandet.
|
||
|
||
### Fixed
|
||
- **Klartext-Leak-Schutz bei Crypto-Degradierung** (`EventSyncEngine`):
|
||
Bei aktivierter Verschlüsselung (`enableEncryption`) und unerreichbarem Vault
|
||
fiel die Engine still auf `NoOpCryptoProvider` zurück (nur `Log.notice`) — und
|
||
`drainOutbox` hätte eingeloggte Account-Daten **unverschlüsselt** gepusht, ohne
|
||
dass die App es merkt. Zwei Ebenen Schutz, beide library-seitig (Apps brauchen
|
||
keine Änderung):
|
||
1. `bootstrapCrypto` loggt jetzt `.error` **und** feuert `onError(error)`, damit
|
||
die App reagieren kann (Banner o.Ä.).
|
||
2. `drainOutbox` pausiert, solange `enableEncryption && crypto == NoOp`
|
||
(`isCryptoDegraded`): Events bleiben sicher in der Outbox statt als Klartext
|
||
rauszugehen. Ein erneut erfolgreicher `bootstrapCrypto` (nächster
|
||
`signIn`/`start`) re-wrapped via `reencryptOutbox` und flusht dann.
|
||
Apps mit `enableEncryption == false` nutzen NoOp bewusst → kein Block.
|
||
Lokaler Betrieb (Reducer/Projektion) läuft unverändert weiter (Local-First).
|
||
- **WS-Reconnect-Jitter** (`SyncWSClient.scheduleReconnect`): Der exponentielle
|
||
Backoff (1→30 s) feuerte ohne Jitter, sodass nach einem flächigen Server-Ausfall
|
||
alle Clients synchron zur selben Sekunde reconnecten (Thundering Herd). Jetzt
|
||
Equal Jitter (50–100 % des Backoffs); der Backoff-Verlauf bleibt deterministisch.
|
||
|
||
### Added
|
||
- **E-4.0** — Paket-Skelett: `ManaEventSync`-Product, Dependency auf
|
||
`ManaCore`, mana-swift-core-Konventionen (swiftformat/swiftlint).
|
||
- **E-4.1** — Geteilte Plumbing aus pageta-native + nutriphi-native
|
||
extrahiert + vereinheitlicht:
|
||
- Core: `EventEnvelope` (+ `withPayload`/`attributed(to:)`, optionale
|
||
`clientId`/`payloadHash` für Wire-Parität), `JSONValue`, `ULID`,
|
||
`JWTSubject`, `EventSyncError`.
|
||
- `EventStorage` (SwiftData) mit parametrierbarem Store-Name,
|
||
App-Group, `additionalModels` + In-Memory-Option für Tests.
|
||
- Transport: `SyncHTTPClient` (mit `X-Schema-Hash` + `422`→
|
||
`schemaOutdated`), optionaler `SyncWSClient`.
|
||
- Crypto: `CryptoProvider`-Protokoll, `NoOpCryptoProvider`,
|
||
`MasterKeyCryptoProvider` (AES-GCM-256, `enc:1:`-Wire), `VaultClient`
|
||
(authBaseURL parametriert) — alles opt-in.
|
||
- **E-4.2** — Anonymous-Mode + Claim:
|
||
- `EventSyncEngine` besitzt die Identität: `makeEnvelope` stempelt
|
||
`attributedToUserId`/`actor` aus dem Auth-Modus (anon `anon:<id>` ODER
|
||
JWT-`sub`) — ersetzt das `currentUserId()`-Chokepoint der Apps.
|
||
- Emit funktioniert offline ohne Login; Push/Pull/WS gated bis
|
||
`signIn(realUserId:)` re-taggt (idempotent) + drained.
|
||
- `AnonymousRetention` (default 10k Events / 90 Tage) inkl. Prune.
|
||
- **E-4.3 (teilweise)** — `X-Schema-Hash`-Header + `422`-Handling.
|
||
Reducer-Golden-Parität gegen `event-kit/test-fixtures` folgt mit der
|
||
ersten App-Migration (E-4.4).
|
||
|
||
- **E-4.4 (Teil 1)** — **pageta-native** ist Erst-Adopter: 9 Plumbing-
|
||
Dateien (~900 LOC) gelöscht, `EventSyncCoordinator` baut Events via
|
||
`engine.makeEnvelope/emit` + liest via `engine.events(forAggregate:)`;
|
||
`init` ist jetzt sync `throws` (Crypto-Bootstrap in `start()`).
|
||
iOS-Sim-Build grün, 38/38 pageta-Tests grün. Store-Name + App-Group
|
||
unverändert → lokale Daten bleiben erhalten.
|
||
|
||
### Tests
|
||
- 18 Tests grün: Core (ULID/JWT/JSONValue/Envelope), Storage (append/
|
||
outbox/**Claim-Reattribute**/Retention/Meta), Crypto (AES-GCM-Round-Trip/
|
||
NoOp/Mixed-Tolerance/Fresh-IV).
|
||
|
||
### Offen
|
||
- Re-Encrypt der geclaimten Outbox-Payloads mit dem Master-Key beim
|
||
`signIn` (heute Mixed-Tolerance: geclaimte Guest-Events liegen plaintext
|
||
auf dem Server, neue verschlüsselt).
|
||
- Wire-Contract-Test gegen den echten Server (`sequenceNumber`
|
||
number-vs-string, `clientId`/`payloadHash`).
|
||
- Voller Engine-Integrationstest (braucht Keychain/AuthClient — heute
|
||
über Storage- + Crypto-Unit-Tests + die App-Migration abgedeckt).
|