Files
claude-wuerth-plato/memory/project_optimistic_locking.md
T
2026-07-09 09:21:42 +02:00

2.5 KiB

name, description, metadata
name description metadata
project-optimistic-locking Server-seitige optimistische Sperre für Customer-Writes (VERSION_CONFLICT) — Fix gegen Last-Write-Wins bei zwei Sales am selben Kunden, eingebaut 2026-06-29.
node_type type originSessionId
memory project 31351046-6fed-46e2-b117-bcc9d8064774

Vor Go-live (Donnerstag 2026-07-02) das Concurrency-Verhalten der Customer-Writes gefixt.

Ausgangslage (charakterisiert): @VersionColumn war im Schreibpfad wirkungslos — persistCustomer machte repo.merge(existing, scalars), das die version aus dem Client-Payload übernahm. Folge: Last-Write-Wins, version lief sogar rückwärts (2→1), stiller Datenverlust bei parallelen Editoren. Jeder Sales hat prinzipiell alle Kunden des Landes → der Fall ist real.

Fix (rein BE, kein FE-Eingriff):

  • libs/shared/src/lib/errors/error-catalog.ts: neuer Code VERSION_CONFLICT (HTTP 409).
  • libs/customer/customer-be/src/lib/customer-core.service.ts: enforceVersion() in persistCustomer vor dem Merge — incoming.version !== existing.versionDomainException('VERSION_CONFLICT', …, forcedState: existing). Danach zählt @VersionColumn wieder hoch.
  • Kernregel: Sperre greift nur, wenn incoming.version != null. Ein nie synchronisierter Neuanlage-Stand (version undefined), der nach Crash erneut gesendet wird, ist ein idempotenter Re-Send — sonst wäre at-least-once / Crash-Recovery (S8b) kaputtgegangen. Ein bereits synchronisierter Datensatz trägt clientseitig immer eine version (reconcile schreibt den Server-Stand lokal zurück).

Verhalten: stale Edit → 409 → FE rollt über den vorhandenen forcedState-Mechanismus (wie SAP-Readonly) auf den Server-Stand zurück, Fehler sichtbar. Verliererseite verliert ihre Änderung (mit Meldung); ein Merge-Dialog wäre eine spätere fachliche Erweiterung.

Wichtig für künftige Fixes: version ist als „wer ist neuer"-Vergleichsbasis untauglich (client-steuerbar). lastUpdateTs ist server-autoritativ (@BeforeUpdateDate.now()) — das ist die verlässliche Basis. Belegt in den Tests.

Tests: customer-core.concurrency.spec.ts (5, BE-direkt gegen In-Memory-SQLite) + sync-loop.integration.spec.ts S10 (End-to-End Multi-Sales). Gesamt 8 Projekte grün. Siehe project-sync-testing für den Test-Harness.

Lokaler Testlauf: node 22 via fnm nötig (eval "$(/opt/homebrew/bin/fnm env)" && fnm use 22), better-sqlite3 baut nicht gegen node 26. node_modules waren nicht installiert → npm ci. Repo: ~/projects/fahrzeugeinrichtung-plato-backend.