mana-swift-event-sync/CHANGELOG.md
Till JS 33d14032ae Release v0.5.0
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 22:41:48 +02:00

94 lines
5.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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).