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

5.1 KiB
Raw Permalink Blame History

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