feat(backend): Historie in zwei Ebenen, gezielter Repository-Zugriff (Schritt 2)
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>
This commit is contained in:
co-authored by
Claude Opus 5
parent
0f3bd8f5b8
commit
d5cdff6058
+31
-15
@@ -61,16 +61,31 @@ werden.
|
||||
|
||||
## Historie & Wiederherstellung
|
||||
|
||||
- Jede Änderung (CREATED, UPDATED, DELETED, RESTORED) wird als Snapshot in
|
||||
einer vom Dokument getrennten Historie protokolliert – sie **überlebt ein
|
||||
DELETE**.
|
||||
- `GET /api/v1/documents/{uuid}/history` liefert alle Einträge (älteste
|
||||
zuerst); Identifier ist die UUID, wie bei GET (der Titel ist nicht eindeutig).
|
||||
- Jede Änderung wird als Snapshot in einer vom Dokument getrennten Historie
|
||||
protokolliert – sie **überlebt ein DELETE**.
|
||||
- **Zwei Ebenen** (D76): **Meilensteine** sind die nutzersichtbare Historie und
|
||||
bleiben; **Sync-Versionen** tragen die Diffs des Live-Editings, sind
|
||||
kurzlebig und werden verdichtet. Ohne die Trennung würde die Historie beim
|
||||
getakteten Schreiben zum Transaktionslog – hunderte Volltext-Snapshots eines
|
||||
40-kB-Dokuments je Sitzung.
|
||||
- Meilenstein wird ein Stand bei einer strukturellen Änderung (Anlegen,
|
||||
Löschen, Wiederherstellen, Rückfall), beim Vollersatz per `PUT` und
|
||||
**nach einer Schreibpause**. Letzteres ohne Zeitgeber: Die nächste
|
||||
Änderung stellt fest, dass eine Pause war, und befördert die Version
|
||||
davor nachträglich. Der Knopfdruck aus dem Konzept ist derselbe
|
||||
Schalter (`milestone = true`), sobald `PATCH /content` ihn durchreicht.
|
||||
- Stellschrauben: `werkbaum.live-editing.milestone-pause` (30 s) und
|
||||
`sync-retention` (1 h).
|
||||
- `GET /api/v1/documents/{uuid}/history` liefert die Meilensteine (älteste
|
||||
zuerst) und immer den jüngsten Stand; Identifier ist die UUID, wie bei GET
|
||||
(der Titel ist nicht eindeutig).
|
||||
- `POST /api/v1/documents/{uuid}/restore` stellt ein gelöschtes Dokument unter
|
||||
derselben UUID wieder her (letzter Stand vor dem Löschen). Mit optionalem
|
||||
Body `{"version": n}` wird eine bestimmte Version wiederhergestellt – das
|
||||
funktioniert auch als Rollback für noch existierende Dokumente; ohne
|
||||
Zielversion antwortet der Server bei existierendem Dokument mit 409.
|
||||
derselben UUID wieder her (`RESTORED`, letzter Stand aus dem Tombstone). Mit
|
||||
optionalem Body `{"version": n}` wird eine bestimmte Version übernommen – bei
|
||||
einem noch lebenden Dokument ist das ein Rückfall (`ROLLED_BACK`), kein
|
||||
Wiederherstellen: Der Client hatte nie eine Sperre. Ohne Zielversion
|
||||
antwortet der Server bei existierendem Dokument mit 409, eine bereits
|
||||
verdichtete Zielversion mit 404.
|
||||
|
||||
## Vorbereitete Erweiterungen
|
||||
|
||||
@@ -81,12 +96,13 @@ werden.
|
||||
Spec; die Behavior-Tests erhalten dann einen Auth-Schritt
|
||||
(„Angenommen ich bin als … angemeldet").
|
||||
|
||||
**Live-Editing**
|
||||
- Jedes Dokument trägt eine `version`, die bei jedem Update inkrementiert
|
||||
wird – Basis für Optimistic Locking (HTTP 409 ist in der Spec bereits
|
||||
reserviert) und für Delta-Synchronisation über WebSocket/STOMP.
|
||||
- `DocumentUpdateRequest.expectedVersion` ist bereits im Vertrag vorgesehen,
|
||||
wird aber noch nicht ausgewertet.
|
||||
**Live-Editing** (Konzept: `docs/live-editing-proposal.md`, Entscheidung: D76)
|
||||
- **Gebaut:** das Zeilen-Diff als reine Funktionen (`de.werkbaum.diff` –
|
||||
Anwenden, Berechnen, Rebasen, Prüfsumme) und die zweistufige Historie.
|
||||
- **Offen:** `PATCH /content` samt Rebase und Idempotenz, der Änderungsfeed per
|
||||
Long Polling, Master-Passwort für `GET /documents`, Client-Anpassung.
|
||||
- `DocumentUpdateRequest.expectedVersion` ist im Vertrag vorgesehen, wird aber
|
||||
noch nicht ausgewertet.
|
||||
|
||||
**Clientseitige Verschlüsselung**
|
||||
- `content` ist ein opaker String, den der Server nie interpretiert. Der
|
||||
|
||||
Reference in New Issue
Block a user