Files
maarcadetweet/crates/pds-server/Cargo.toml
T
tomdeboneandClaude Opus 5 0646fbeebe 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
2026-09-10 07:08:02 +02:00

63 lines
1.6 KiB
TOML

[package]
name = "pds-server"
version.workspace = true
edition.workspace = true
license.workspace = true
authors.workspace = true
description = "maarcadetweet PDS server (bin)"
[lints.rust]
unsafe_code = "forbid"
[[bin]]
name = "pds-server"
path = "src/main.rs"
[dependencies]
tokio = { workspace = true }
dotenvy = { workspace = true }
axum = { workspace = true, features = ["ws"] }
tower = { workspace = true }
tower-http = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
tracing = { workspace = true }
tracing-subscriber = { workspace = true }
anyhow = { workspace = true }
sqlx = { workspace = true }
chrono = { workspace = true }
at-shared = { workspace = true }
at-crypto = { workspace = true }
at-identity = { workspace = true }
at-lexicon = { workspace = true }
at-repo = { workspace = true }
at-mst = { workspace = true }
at-blob = { workspace = true }
argon2 = { workspace = true }
ciborium = { workspace = true }
hex = { workspace = true }
rand = { workspace = true }
uuid = { workspace = true }
bytes = { workspace = true }
futures = { workspace = true }
cid = { workspace = true }
k256 = { workspace = true }
p256 = { workspace = true }
unsigned-varint = "0.8"
url = { workspace = true }
reqwest = { workspace = true }
[dev-dependencies]
tokio = { workspace = true }
reqwest = { workspace = true }
serde_json = { workspace = true }
uuid = { workspace = true }
cid = { workspace = true }
sha2 = { workspace = true }
hex = { workspace = true }
sqlx = { workspace = true }
at-crypto = { workspace = true }
futures = { workspace = true }
tokio-tungstenite = { workspace = true }
chrono = { workspace = true }