94 lines
5.1 KiB
Markdown
94 lines
5.1 KiB
Markdown
# Changelog
|
||
|
||
Alle nennenswerten Änderungen an `ManaEventSync`. Semver strikt.
|
||
|
||
## [Unreleased]
|
||
|
||
## [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).
|