feat(collab): log-compactie van de op-log (§5.2, #1007) #1009

Merged
brenno merged 4 commits from collab/log-compaction into main 2026-07-31 08:55:19 +00:00
Owner

Closes #1007.

Vervolg op #1005 (COLLABORATION.md §5.2). Herbaselijning maakte bijkomen snel maar
liet de oude op-records staan, zodat de sidecar-op-log onbegrensd bleef groeien.
Deze increment ruimt ze op. De client krijgt hiervoor zijn eerste verwijderende
uitgaande operatie
, dus dit is ontworpen na een security-architect-review (F1–F7)
en die eisen zijn hard ingebouwd. Raakt het .md niet (P2).

Ontwerp

  • WebDAV DELETE, ingesloten. WebdavService.delete gaat langs dezelfde
    beveiligde weg als upload/download — de NetGuard-gepinde HttpClient,
    followRedirects=false (een 3xx is een fout, nooit "verwijderd"), en
    uriFor-containment. CollabLogStore.delete(seq) voedt het uitsluitend
    _pathFor(seq), dus het kan alleen een genummerd recordbestand raken — nooit de
    snapshot, het baken of het .md. Idempotent (404 = al weg). Een mislukte delete
    laat het record staan (de veilige toestand).
  • Compactie, één interval achterlopend. Bij een herbaselijning op seq S wist
    de autoriteit de records die de vorige basislijn al omvatte (≤ die seq). Het
    verwijdervenster schuift pas op ná een bevestigd-duurzame snapshot (F1), dus
    een mislukte snapshot-schrijf kan het venster nooit verbreden en niemand doen
    stranden. Deletes per cyclus begrensd (F5), ná de beacon-heartbeat.
  • Strand-recovery. Wie tóch te ver achterloopt en op een weggehaald record
    wacht (!isCaughtUp terwijl de laatste snapshot een hogere versie draagt),
    herbaselijnt zichzelf: hij leest de snapshot opnieuw en springt sessie + transport
    vooruit — alleen vooruit en fail-closed (een stale of half-geschreven
    snapshot laat hem staan om het opnieuw te proberen).

Residu (gedocumenteerd)

Bij de zeldzame strand-recovery worden niet-geëchode lokale edits overschreven door
de sprong naar de autoritatieve staat — dezelfde klasse als het §5.3-failover-residu,
aanvaardbaar omdat zo'n deelnemer ver achterlag en her-synchroniseren beter is dan
gestrand blijven.

Reikwijdte

webdav_service.dart (delete), collab_log_store.dart (delete),
handover_coordinator.dart (compactie + strand-detectie), collab_session.dart
(rebaseTo, forward-only), webdav_async_transport.dart (jumpTo, forward-only).
Geen l10n (onzichtbaar), geen afhankelijkheid.

Toetsing

Ontworpen na een security-architect-ontwerpreview (zeven eisen F1–F7, alle hard
ingebouwd), daarna een security- en een bewaker-diffreview. De diffreview bracht
één echte tekortkoming naar boven: compactie liep op het kritieke pad van de
poll-lus, zodat een reeks trage DELETEs de vólgende heartbeat kon vertragen en
spontane overdracht kon uitlokken. Opgelost door compactie losgekoppeld van de
lus te draaien (niet meer onder _syncing), plus een test voor het meerdere-passes-
pad. Nieuwe tests o.a.: DELETE op het genummerde pad + 404-idempotent + 3xx-als-fout;
compactie wist de records van de vorige basislijn; de F1-stall-guard (een mislukte
snapshot-schrijf compacteert niets); een gestrande deelnemer die via de snapshot
herstelt; fail-closed bij een onleesbare snapshot; en een grote achterstand die over
meerdere begrensde passes wordt geruimd. make check groen, make check-secrets +
make sast schoon. Geen goldens (geen rendering).

🤖 Generated with Claude Code

Closes #1007. Vervolg op #1005 (COLLABORATION.md §5.2). Herbaselijning maakte bijkomen snel maar liet de oude op-records staan, zodat de sidecar-op-log onbegrensd bleef groeien. Deze increment ruimt ze op. De client krijgt hiervoor zijn **eerste verwijderende uitgaande operatie**, dus dit is ontworpen na een security-architect-review (F1–F7) en die eisen zijn hard ingebouwd. Raakt het `.md` niet (P2). ## Ontwerp - **WebDAV DELETE, ingesloten.** `WebdavService.delete` gaat langs dezelfde beveiligde weg als upload/download — de NetGuard-gepinde `HttpClient`, `followRedirects=false` (een 3xx is een fout, nooit "verwijderd"), en `uriFor`-containment. `CollabLogStore.delete(seq)` voedt het uitsluitend `_pathFor(seq)`, dus het kan alleen een genummerd recordbestand raken — nooit de snapshot, het baken of het `.md`. Idempotent (404 = al weg). Een mislukte delete laat het record staan (de veilige toestand). - **Compactie, één interval achterlopend.** Bij een herbaselijning op seq `S` wist de autoriteit de records die de *vorige* basislijn al omvatte (≤ die seq). Het verwijdervenster schuift pas op **ná een bevestigd-duurzame snapshot** (F1), dus een mislukte snapshot-schrijf kan het venster nooit verbreden en niemand doen stranden. Deletes per cyclus begrensd (F5), ná de beacon-heartbeat. - **Strand-recovery.** Wie tóch te ver achterloopt en op een weggehaald record wacht (`!isCaughtUp` terwijl de laatste snapshot een hogere versie draagt), herbaselijnt zichzelf: hij leest de snapshot opnieuw en springt sessie + transport vooruit — **alleen vooruit** en **fail-closed** (een stale of half-geschreven snapshot laat hem staan om het opnieuw te proberen). ## Residu (gedocumenteerd) Bij de zeldzame strand-recovery worden niet-geëchode lokale edits overschreven door de sprong naar de autoritatieve staat — dezelfde klasse als het §5.3-failover-residu, aanvaardbaar omdat zo'n deelnemer ver achterlag en her-synchroniseren beter is dan gestrand blijven. ## Reikwijdte `webdav_service.dart` (`delete`), `collab_log_store.dart` (`delete`), `handover_coordinator.dart` (compactie + strand-detectie), `collab_session.dart` (`rebaseTo`, forward-only), `webdav_async_transport.dart` (`jumpTo`, forward-only). Geen l10n (onzichtbaar), geen afhankelijkheid. ## Toetsing Ontworpen na een security-architect-ontwerpreview (zeven eisen F1–F7, alle hard ingebouwd), daarna een security- en een bewaker-diffreview. De diffreview bracht één echte tekortkoming naar boven: compactie liep op het kritieke pad van de poll-lus, zodat een reeks trage DELETEs de vólgende heartbeat kon vertragen en spontane overdracht kon uitlokken. Opgelost door compactie **losgekoppeld** van de lus te draaien (niet meer onder `_syncing`), plus een test voor het meerdere-passes- pad. Nieuwe tests o.a.: DELETE op het genummerde pad + 404-idempotent + 3xx-als-fout; compactie wist de records van de vorige basislijn; de F1-stall-guard (een mislukte snapshot-schrijf compacteert niets); een gestrande deelnemer die via de snapshot herstelt; fail-closed bij een onleesbare snapshot; en een grote achterstand die over meerdere begrensde passes wordt geruimd. `make check` groen, `make check-secrets` + `make sast` schoon. Geen goldens (geen rendering). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
WebdavService.delete — the client's first data-mutating outbound verb — runs
through the same NetGuard-pinned, followRedirects=false path as upload, treats a
3xx as a failure (never a 'deleted'), and is idempotent (404 = already gone).
CollabLogStore.delete(seq) feeds it only _pathFor(seq), so it can only ever remove
a numbered record file — never the snapshot, beacon or .md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CollabSession.rebaseTo(deck, version) and WebdavAsyncTransport.jumpTo(seq) move a
participant forward to a later snapshot when compaction leaves a gap it cannot
cross. Both are forward-only, so a stale or half-read snapshot can never rewind
the session or replay an op.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The authority deletes records the *previous* durable baseline subsumed (a
one-interval margin), and the delete window widens only after a snapshot write
confirms — so a failed write can never strand a straggler. Deletes run detached
from the poll loop, so a batch of slow DELETEs never delays a heartbeat and makes
peers think the authority dropped. Strand-recovery: a participant whose poll stalls
at a compacted gap the latest snapshot subsumes re-reads it and jumps forward,
fail-closed (a broken snapshot leaves it stranded to retry).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
docs(collab): document log compaction and its DELETE capability (#1007)
All checks were successful
scans / scans (pull_request) Successful in 3m35s
67f27fae89
COLLABORATION.md §5.2 gains the compaction mechanism (contained DELETE, one-interval
margin, detached deletes, forward-only fail-closed strand-recovery) and its residual;
SECURITY_DESIGN.md names the new mutating verb; SOURCE_MAP and CHANGELOG updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
brenno merged commit a484b53d06 into main 2026-07-31 08:55:19 +00:00
Sign in to join this conversation.
No description provided.