Ein DAG-CBOR-Link ist tag(42) um einen Bytestring aus `0x00 || <CID>`. Der CAR-Header taggte bisher die nackte CID ohne das 0x00 — keine spec-konforme CAR-Bibliothek kann dem folgen: sie liest das erste Byte als CID-Version und gibt auf. Betroffen war jede Antwort von getRepo, getBlocks und getRecord. Der Header ist nicht content-adressiert — nichts hasht ihn, keine CID hängt an seinen Bytes. Die Korrektur ändert also ausschließlich, was über die Leitung geht, und keinen einzigen Identifier. Deshalb ist sie hier gemacht und nicht auf eine große Migration vertagt. decode_header akzeptiert weiterhin beide Schreibweisen, damit ein gespeicherter Repo-Export aus einem älteren Build lesbar bleibt. Das ist eindeutig und kein Raten: eine echte CID beginnt nie mit 0x00, da steht das Versions-Varint und Version 0 gibt es nicht. Nebenbei: sync_list_repos_keyset_pagination lief von ganz vorn durch die repos-Tabelle (inzwischen 4900 Zeilen) und riss bei zwei Zeilen pro Seite den eigenen Iterationsdeckel — rot wegen Tabellengröße, nicht wegen Paginierung. Der Test prüft jetzt die Invarianten, um die es geht: Erreichbarkeit jedes DIDs über einen unmittelbar davor gesetzten Cursor, streng aufsteigende Reihenfolge, keine Dubletten, und der zurückgegebene Cursor ist der letzte DID der Seite. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013HC9HLrUU1LNwkzp8nkDLX
maarcadetweet
AT-Protocol-PDS in Rust + AppView + Tauri/Svelte-Desktop-Client.
Posts sind auf 160 Zeichen limitiert (oldschool Twitter), erzwungen durch eigenes Lexicon app.twi.post.
Architektur
crates/
├── at-lexicon/ Lexicon-Schemas + 160-Char-Validierung
├── at-crypto/ k256, p256, CID, multibase, JWT, PLC-Ops, Repo-Signing
├── at-identity/ DID, PLC, Handle-Resolution
├── at-mst/ Merkle-Search-Tree
├── at-repo/ Repos, Commits, Blöcke, TID-Revs
├── at-blob/ S3-kompatibler Blob-Store (MinIO) + In-Memory-Store
├── at-firehose/ Jetstream-Consumer (WebSocket)
├── at-shared/ Config, Errors, DID, Cursor
├── pds-server/ axum HTTP PDS (bin)
└── appview/ Jetstream-Indexer + REST-API (bin)
crates/tauri-app/ Tauri 2 + Svelte 5 + Vite + TS Desktop-Client
├── src/ Svelte-Components (Terminal, StatusBar, NavRail, PostCard, ComposeBox,
│ LoginScreen, ProfileView, NotificationsView)
├── src/lib/styles/ tokens.css (1:1 vom maarcade-Design)
└── src-tauri/ Rust-IPC-Layer
lexicons/app/twi/post.json Custom Lexicon mit maxLength: 160
migrations/pds/ PDS-DB-Schema (users, repos, blobs, sessions, plc_ops)
migrations/appview/ AppView-DB-Schema (posts, likes, follows, notifications, profiles, jetstream_cursor)
docs/ Deployment, Architektur, Tauri-Release (siehe unten)
Setup
# 1) Datenbanken + MinIO starten
docker compose up -d
# 2) Umgebungsvariablen — beide Binaries laden .env selbst (dotenvy).
# PDS_JWT_SECRET muss Hex sein: openssl rand -hex 32
cp .env.example .env
# 3) Workspace kompilieren + Tests
cargo test --workspace
cargo check --workspace
# 4) Tauri-Frontend (Vite dev)
cd crates/tauri-app
npm install
npm run dev
# → http://127.0.0.1:1420
# 5) PDS / AppView (eigene Terminals)
cargo run -p pds-server
cargo run -p appview
Status
| Phase | Stand |
|---|---|
| 0 Foundation, Workspace, Migrations, Lexicon, Crypto | ✅ done |
| 1 Identity (PLC-Ops vollständig signieren) | ✅ done — did:plc: deterministisch aus signed op CID |
| 2 MST + Repo (Spec-konforme CBOR-Encoding) | ✅ done — encode_key = base64url(sha256(raw_key)) per atproto-Spec, split_around/wrap_with_split threaden den recursive right_sub korrekt als k_tree weiter. 27 MST + 13 Repo + 4 Commit Tests grün. |
| 3 PDS-Server (com.atproto.* XRPC) | ✅ done — createAccount/Session/Refresh, createRecord/deleteRecord, like/repost, follow |
| 4 AppView-Foundation (Jetstream-Index) | ✅ done — Jetstream-Indexer + identity-Event-Backfill + PLC-handle-sync-Worker |
| 5 AppView-REST-API | ✅ done — timeline, profile (by-did + by-handle), search, post-by-uri, thread, notifications, followers/following |
| 6 Tauri-UI-Logik an Backend koppeln | ✅ done — LoginScreen, NavRail, PostCard, ComposeBox, Profile/Compose/Search/Settings-Views |
| 7 Polish (Tray, Notifications, Auto-Update) | ✅ done — Tray-Icon custom (tauri::include_image!), Notification-Click navigiert via app://notification-Event + openThread-Helper zu Thread-Detail, Auto-Update in Dev inert (Production-Weg: docs/tauri-release.md) |
| 8 Social-Graph + Benachrichtigungen | ✅ done — notifications-Tabelle, Schreibpfad im Jetstream-Indexer (idempotent, keine Selbst-Notifications), `/api/notifications[/count |
| 9 Auth + Performance | ✅ done — AppView prüft Bearer-Tokens (ES256, Schlüssel aus dem neuen /.well-known/did.json der PDS, fail closed); Timeline und Notifications nur noch für die eigene DID; CORS-Allowlist statt Any; Indizes für Handle-Lookup und Cold-Start-Feed |
Tests
cargo test --workspace # Rust: PDS, AppView, at-* Crates
cd crates/tauri-app && npx vitest run # Svelte/TS
cd crates/tauri-app/src-tauri && cargo test # Tauri-IPC (eigener Workspace!)
Stand zuletzt gegen den lokalen Dev-Stack (docker compose + laufender PDS + AppView): Rust-Workspace grün (u.a. 27 MST, 24 PDS-Integration, 49 AppView-Lib, 14 AppView-Integration), Frontend grün. Zwei Vorbehalte:
- Die DB-gestützten Tests sind fail-open: ohne
DATABASE_URL_APPVIEWin der Umgebung überspringen sie sich selbst und melden das nur auf stderr.cargo test --workspacein einer nackten Shell meldet dann grün, ohne sie ausgeführt zu haben — für einen aussagekräftigen Laufset -a; . ./.env; set +avoranstellen und PDS + AppView laufen lassen. - Läuft die Auth (Default), holen sich die Integrationstests echte Tokens von der PDS
bzw. signieren sie aus
PDS_JWT_SECRET; ohne erreichbare PDS überspringen sie.
crates/tauri-app/src-tauri hat ein eigenes [workspace] und ist nicht Teil des
Root-Workspace; cargo test --workspace von oben erfasst den IPC-Layer nicht.
Dokumentation
| Datei | Inhalt |
|---|---|
docs/architecture.md |
Crate-Verantwortlichkeiten, Datenfluss PDS → Jetstream → AppView → Client, Tabellenübersicht |
docs/deployment.md |
Betrieb: docker compose, Migrationen, alle Env-Variablen, Release-Build, systemd-Units, Reverse-Proxy, Health-Checks |
docs/tauri-release.md |
Signing-Keys, Updater-Config, latest.json, Build pro Plattform, Artefaktpfade |
Bekannte Lücken
- Die eigene PDS speist keinen Firehose (
com.atproto.sync.subscribeReposfehlt) — eigene Records erreichen die AppView nur über den Best-Effort-PushPOST /internal/ingest-commit. audwird beim Token-Check nicht validiert (Signatur, Ablauf,scopeundsubschon).- Notifications werden nie gelöscht: Unlike/Unfollow lässt die Zeile stehen, und der Dedupe-Key macht sie „einmal pro (Empfänger, Autor, Art, Subject) für immer".
- Auto-Update ist nur dokumentiert, nicht verdrahtet: niemand ruft
check()auf, das Updater-Plugin ist nicht installiert (siehedocs/tauri-release.md). - Reply-Notifications gehen verloren, wenn die Antwort vor ihrem Parent indiziert wird (kein Nachlauf) — bei Jetstream möglich.
at-blobspricht MinIO ohne Signature V4 — echtes AWS S3 funktioniert damit nicht.
Design
Orange Akzent, IBM Plex Mono, schwarzer Hintergrund mit 3%-Grid, Terminal-Fenster-Component mit blinkendem Cursor. Tokens sind 1:1 von maarcade-shell/landing/assets/css/tokens.css abgeleitet, plus zwei neue Repos-Tokens (--cid-fg, --rev-fg).