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
63 lines
1.6 KiB
TOML
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 }
|