Der Server rebased selbst: Ist die Basis veraltet, ueberschneiden sich die
Operationen aber nicht mit den zwischenzeitlichen, verschiebt er sie und
akzeptiert. Reines Ablehnen fuehrte zu Starvation — ein Client mit hoher
Latenz kaeme bei fleissigen Mitschreibern womoeglich nie durch. 409 gibt es
nur bei echter Ueberschneidung, mit allem, was der Client zum Weiterarbeiten
braucht, ohne neu zu laden.
Pruefsumme ist Pflicht (422 bei Abweichung): Die Versionsnummer bestaetigt
nur, dass die Basis dieselbe Version ist, nicht dass beide Seiten sie gleich
lesen. clientId + seq machen den Aufruf wiederholbar — im Mobilnetz ist die
verlorene Antwort der Normalfall.
Die Sperre je Dokument liegt ausserhalb der Transaktion: innen gaebe der
Proxy sie vor dem Commit frei, und der naechste Schreiber laese einen Stand,
der noch nicht steht. Deshalb ist LiveEditingService nicht transaktional und
schreibt ueber DocumentService.
Was das Konzept offenliess, ist jetzt entschieden und in D76-Nachtrag 4
begruendet: die Randfaelle der Einfuege-Ueberschneidung, die Trennung von
400 und 422, die gedeckelte Idempotenz im Speicher.
104 Tests, davon 8 Cucumber-Szenarien fuer das Live-Editing. Gegenprobe:
Pruefsumme nicht geprueft, Idempotenz entfernt, veraltete Basis abgelehnt
statt verschoben -> es fallen jeweils genau die danach benannten.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Meilensteine sind die nutzersichtbare Historie und bleiben; Sync-Versionen
tragen die Diffs des Live-Editings und werden nach der Aufbewahrungsfrist
verdichtet (D76). Ohne die Trennung wuerde die Historie beim getakteten
Schreiben zum Transaktionslog.
Die Schreibpause braucht keinen Zeitgeber: Die naechste Aenderung stellt
fest, dass eine Pause war, und befoerdert die Version davor nachtraeglich.
Strukturelle Aenderungen sind immer Meilensteine.
Das Historie-Repository greift jetzt gezielt zu (eine Version, juengster,
aeltester, Meilensteine, maxVersion) statt stets alle Eintraege zu laden und
in Kotlin zu filtern — bei hunderten Volltext-Versionen je Dokument war das
untragbar. Restore liest den letzten Stand aus dem Tombstone: der ueberlebt
das Verdichten, die Version davor womoeglich nicht.
Dabei die D76-Unschaerfe aufgeloest: RESTORED heisst nur noch "ein
geloeschtes Dokument ist wieder da" (der Client hebt seine Sperre auf), der
Rueckfall eines lebenden Dokuments ist ROLLED_BACK.
81 Tests. Gegenprobe: Schreibpause ignoriert -> genau die danach benannte
Zusicherung faellt; Rueckfall wieder als RESTORED -> Unit- und
Cucumber-Test dazu; juengster Stand aus der Historie genommen -> genau einer.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
backend/CLAUDE.md sah Kotest-Assertions vor, der Code benutzte aber die
JUnit-Assertions. Umgestellt bei 25 Aufrufen in zwei Dateien - jetzt billig,
und Test-Abhaengigkeiten sind unkritisch, weil sie in keinem Artefakt landen.
Wo Kotest spezifischer ist, ersetzt es die handgeschriebenen Meldungen:
shouldContain statt assertTrue(contains, "..."), withClue nur noch dort, wo
der Kontext wirklich hilft.
Gegenprobe per Mutation: eine falsche Erwartung im Service-Test und eine im
BDD-Schritt lassen genau die danach benannten Tests fallen.
CLAUDE.md berichtigt: Das Backend ist nicht mehr "noch nicht bootstrapped",
die Konfiguration heisst application.yaml, die Schichten entsprechen dem
tatsaechlichen Aufbau, und die drei Spring-Boot-4-Fallen von heute sind
festgehalten, damit sie niemanden erneut treffen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>