feat(pds): com.atproto.sync.subscribeRepos — lokaler Firehose

Bisher erreichten eigene Records die AppView nur über den Best-Effort-Push
/internal/ingest-commit. Ging der verloren (AppView kurz weg, Netzwerk-
fehler), war der Post dauerhaft weg: der öffentliche Jetstream kennt diese
PDS nicht, es gab also keinen zweiten Weg.

Jeder Commit schreibt sein Event in derselben Transaktion nach
firehose_events. Damit kann es keinen Commit ohne Event geben — und keine
Sequenz ohne Commit.

Die seq muss lückenfrei sein, sonst ist sie als Cursor wertlos: BIGSERIAL
vergibt Nummern bei INSERT, nicht bei COMMIT, also können zwei Schreiber 5
und 6 ziehen und in umgekehrter Reihenfolge sichtbar werden — ein Leser
dazwischen sieht 6, merkt sich das und erfährt von 5 nie. Ein globaler
pg_advisory_xact_lock unmittelbar vor dem INSERT erzwingt Commit-Reihenfolge
== seq-Reihenfolge. Er wird nach dem per-Repo-FOR-UPDATE genommen, überall in
derselben Reihenfolge, also ohne Deadlock-Risiko. Preis: das Ende jeder
schreibenden Transaktion ist global serialisiert; das steht im Modulkopf.

Der WebSocket-Handler abonniert den Broadcast, *bevor* er die Datenbank
liest, und filtert Live-Events auf seq > Wasserstand. Aus einem Rennen wird
so eine Dublette, die sich filtern lässt, statt einer Lücke, die es nicht
gibt. Ein zu langsamer Consumer bekommt #info/OutdatedCursor und fällt auf
den DB-Replay zurück, statt getrennt zu werden — die Events sind durabel,
also ist der Rückfall verlustfrei.

Frame-Hülle ist konformes DAG-CBOR mit Tag-42-Links (neues Modul dag_cbor,
aus car.rs herausgezogen statt dupliziert). Die Blöcke darin behalten die
Konvention dieses Repos: CIDs als Strings. Ein fremder Consumer liest die
Frames, scheitert aber an den Blockinhalten — das zu ändern hieße, jede CID
im System zu ändern, inklusive der did:plc-Ableitung. Steht so im Modulkopf.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013HC9HLrUU1LNwkzp8nkDLX
This commit is contained in:
tomdebone
2026-09-10 07:08:02 +02:00
co-authored by Claude Opus 5
parent 6fd046417a
commit 0646fbeebe
14 changed files with 2844 additions and 22 deletions
+5 -1
View File
@@ -16,7 +16,7 @@ path = "src/main.rs"
[dependencies]
tokio = { workspace = true }
dotenvy = { workspace = true }
axum = { workspace = true }
axum = { workspace = true, features = ["ws"] }
tower = { workspace = true }
tower-http = { workspace = true }
serde = { workspace = true }
@@ -39,6 +39,7 @@ hex = { workspace = true }
rand = { workspace = true }
uuid = { workspace = true }
bytes = { workspace = true }
futures = { workspace = true }
cid = { workspace = true }
k256 = { workspace = true }
p256 = { workspace = true }
@@ -56,3 +57,6 @@ sha2 = { workspace = true }
hex = { workspace = true }
sqlx = { workspace = true }
at-crypto = { workspace = true }
futures = { workspace = true }
tokio-tungstenite = { workspace = true }
chrono = { workspace = true }