this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

init

Vysakh Premkumar 38a75f36

+774
+546
CLAUDE.md
··· 1 + # Decentralized Library API Gateway: Technical Architecture Specification 2 + 3 + > **For conceptual overview, workflows, and non-technical explanations, see [`humandocs.md`](./humandocs.md).** 4 + 5 + > **Scope**: This document contains implementation-level specifications only — code patterns, schemas, configurations, threat models, and performance contracts. It is intended for engineers building or maintaining the system. 6 + 7 + --- 8 + 9 + ## 1. Authentication: DPoP-Bound AT Protocol OAuth 2.0 10 + 11 + ### Implementation (axum + async-graphql) 12 + 13 + ```rust 14 + pub struct AtprotoAuth { 15 + pds_discovery: PdsDiscoveryCache, // TTL-cached DID → PDS mapping 16 + token_verifier: JwtVerifier, // Validates DPoP-bound JWTs 17 + scope_enforcer: ScopeValidator, // Enforces lexicon-level permissions 18 + } 19 + 20 + impl AtprotoAuth { 21 + pub async fn validate(&self, headers: &HeaderMap) -> Result<AuthContext, AuthError> { 22 + let dpop_proof = headers.get("DPoP") 23 + .ok_or(AuthError::MissingDpop)?; 24 + let token = headers.get("Authorization") 25 + .and_then(|v| v.to_str().ok()) 26 + .and_then(|v| v.strip_prefix("DPoP ")) 27 + .ok_or(AuthError::InvalidAuthHeader)?; 28 + 29 + let claims = self.token_verifier.verify_dpop_bound(token, dpop_proof).await?; 30 + let did = claims.sub; 31 + 32 + let did_doc = self.pds_discovery.resolve(&did).await?; 33 + if did_doc.signing_key != claims.key_id { 34 + return Err(AuthError::KeyRotationDetected); 35 + } 36 + 37 + self.scope_enforcer.validate(&claims.scope, requested_lexicon)?; 38 + 39 + Ok(AuthContext { did, scopes: claims.scope }) 40 + } 41 + } 42 + ``` 43 + 44 + ### Threat Model 45 + 46 + | Threat | Mitigation | 47 + |--------|-----------| 48 + | Token replay | DPoP proof binding + 15-min JWT TTL | 49 + | DID takeover | Real-time DID document validation + signature verification | 50 + | Scope escalation | Server-side lexicon permission enforcement | 51 + | PDS impersonation | TLS + certificate pinning for PDS endpoints | 52 + 53 + ### Auth Configuration 54 + 55 + - DPoP binding: **required** on all authenticated endpoints 56 + - DID resolution cache TTL: **300s** (forced refresh on auth failure) 57 + - Lexicon scope format: `com.example.library.<collection>:<action>` (e.g., `poolItem:create`, `loanAgreement:read`) 58 + 59 + --- 60 + 61 + ## 2. Firehose Subscription: Federation Sync Engine 62 + 63 + ### Implementation 64 + 65 + ```rust 66 + pub struct FirehoseSubscriber { 67 + relay_endpoints: Vec<Url>, 68 + cursor_store: CursorPersistence, // PostgreSQL-backed 69 + backfill_queue: BackfillManager, // com.atproto.sync.getRepo for gaps 70 + record_processor: AsyncRecordPipeline, 71 + } 72 + 73 + impl FirehoseSubscriber { 74 + pub async fn run(&mut self) -> Result<(), SubscriptionError> { 75 + loop { 76 + let mut ws = self.connect_with_retry().await?; 77 + let cursor = self.cursor_store.load().await?; 78 + ws.send(SubscribeRepos { cursor: Some(cursor) }).await?; 79 + 80 + while let Some(event) = ws.next().await { 81 + match event { 82 + Event::Commit(commit) => { 83 + if !self.verify_commit_signature(&commit).await { 84 + log::warn!("Invalid signature for DID {}", commit.repo); 85 + continue; 86 + } 87 + if self.cursor_store.is_processed(&commit.repo, &commit.rev).await { 88 + continue; 89 + } 90 + self.process_commit_ops(commit).await?; 91 + self.cursor_store.save(&commit.repo, &commit.rev).await?; 92 + } 93 + Event::GapDetected { since, to } => { 94 + self.backfill_queue.enqueue(commit.repo, since, to).await; 95 + } 96 + } 97 + } 98 + 99 + self.relay_endpoints.rotate_left(1); 100 + tokio::time::sleep(Duration::from_secs(5)).await; 101 + } 102 + } 103 + } 104 + ``` 105 + 106 + ### Cursor Persistence Schema 107 + 108 + ```sql 109 + CREATE TABLE firehose_cursors ( 110 + did TEXT PRIMARY KEY, 111 + rev TEXT NOT NULL, 112 + seq BIGINT NOT NULL, 113 + processed_at TIMESTAMPTZ DEFAULT now(), 114 + UNIQUE(did, rev) 115 + ); 116 + 117 + -- Idempotent upsert 118 + INSERT INTO firehose_cursors (did, rev, seq) 119 + VALUES ($1, $2, $3) 120 + ON CONFLICT (did) DO UPDATE SET rev = $2, seq = $3, processed_at = now() 121 + WHERE firehose_cursors.seq < $3; 122 + ``` 123 + 124 + ### Operational Metrics 125 + 126 + ```prometheus 127 + atproto_firehose_events_processed_total{lexicon="com.example.library.*"} 128 + atproto_firehose_cursor_lag_seconds{did="..."} 129 + atproto_firehose_signature_failures_total 130 + atproto_backfill_queue_depth 131 + atproto_backfill_success_rate 132 + ``` 133 + 134 + ### Reliability Constraints 135 + 136 + - Cursor persistence: PostgreSQL with `ON CONFLICT` idempotency 137 + - Relay failover: 2-3 endpoints, rotate on connection failure 138 + - Backfill: `com.atproto.sync.getRepo` for CAR file retrieval on gap detection 139 + - Signature verification: mandatory pre-processing gate 140 + - Ordering: per-DID event ordering via Tokio task pools 141 + 142 + --- 143 + 144 + ## 3. Data Model & Conflict Resolution 145 + 146 + ### GraphQL Schema 147 + 148 + ```graphql 149 + type Book { 150 + workId: String! # e.g., "wikidata:Q190192" or "isbn-group:978-0-441" 151 + edition: BookEdition! 152 + availableEditions: [BookEdition!]! 153 + } 154 + 155 + type BookEdition { 156 + uri: String! # AT-URI: at://did:plc:.../com.example.library.book/... 157 + isbn: String 158 + editionLabel: String 159 + publisher: String 160 + publicationYear: Int 161 + coverArtCid: String 162 + } 163 + ``` 164 + 165 + ### Book Canonicalization Resolution Logic 166 + 167 + 1. On `addBookToPool`: Query Wikidata/OpenLibrary for ISBN → extract ```_workId_TICK 168 + 2. Check local DB for existing `Book` with same ```_workId_TICK 169 + 3. If found: link `poolItem` to existing `Book`, create new ```_BookEdition_TICK 170 + 4. If not: create `Book` + ```_BookEdition_TICK 171 + 172 + ### Pool Query (SQL) 173 + 174 + ```sql 175 + SELECT b.work_id, array_agg(e.edition_label) as editions, 176 + COUNT(pi.uri) as available_copies 177 + FROM books b 178 + JOIN book_editions e ON b.id = e.book_id 179 + JOIN pool_items pi ON e.uri = pi.book_edition_uri 180 + WHERE pi.status = 'AVAILABLE' 181 + GROUP BY b.work_id 182 + ORDER BY available_copies DESC; 183 + ``` 184 + 185 + ### Loan Status State Machine 186 + 187 + ```rust 188 + pub enum LoanStatus { 189 + PendingApproval, 190 + Active, 191 + Returned, 192 + Overdue, // Auto-set when expected_return_date < now() 193 + ReportedLost, // Lender-initiated 194 + Disputed, // Borrower contests loss 195 + Resolved, // Terminal: compensated or forgiven 196 + } 197 + ``` 198 + 199 + ### Lost Book Mutation 200 + 201 + ```rust 202 + pub async fn report_book_lost( 203 + ctx: &Context, 204 + loan_uri: String, 205 + resolution: LossResolution, // Compensate | Forgive | Investigate 206 + ) -> Result<LoanAgreement> { 207 + ensure!(ctx.did == loan.lender_did, AuthError::Forbidden); 208 + 209 + let record = LoanAgreementRecord { 210 + status: LoanStatus::ReportedLost, 211 + loss_reported_at: Some(Utc::now()), 212 + resolution: Some(resolution), 213 + ..loan.record 214 + }; 215 + self.pds_client.put_record(&loan_uri, &record).await?; 216 + 217 + if matches!(resolution, LossResolution::Compensate) { 218 + self.reputation_service.apply_penalty( 219 + &loan.borrower_did, 220 + ReputationPenalty::LostBook 221 + ).await; 222 + } 223 + 224 + self.indexer.remove_pool_item(&loan.book_copy_uri).await; 225 + Ok(loan) 226 + } 227 + ``` 228 + 229 + ### Consistency Guarantees 230 + 231 + - Primary key: AT-URI (`at://did:plc:.../collection/rkey`) 232 + - Canonicalization: work-level grouping, edition-specific loans 233 + - Idempotency: `rev`-based deduplication on all record writes 234 + 235 + --- 236 + 237 + ## 4. Caching & Rate Limiting 238 + 239 + ### Three-Tier Cache 240 + 241 + ```rust 242 + pub struct CacheLayer { 243 + l1_cache: DashMap<CacheKey, CachedValue>, // In-memory LRU, 10k entries, 5-min TTL 244 + l2_cache: RedisPool, // Distributed, 1-hour TTL 245 + db: PgPool, // Source of truth 246 + } 247 + 248 + impl CacheLayer { 249 + pub async fn get_pool_items(&self, query: PoolQuery) -> Result<Vec<PoolItem>> { 250 + if let Some(items) = self.l1_cache.get(&query) { 251 + return Ok(items.clone()); 252 + } 253 + if let Some(items) = self.l2_cache.get(&query).await? { 254 + self.l1_cache.insert(query.clone(), items.clone()); 255 + return Ok(items); 256 + } 257 + let items = self.db.query_pool_items(query.clone(), LIMIT).await?; 258 + let items_clone = items.clone(); 259 + tokio::spawn(async move { 260 + self.l2_cache.set(&query, &items_clone, TTL_1H).await.ok(); 261 + self.l1_cache.insert(query, items_clone); 262 + }); 263 + Ok(items) 264 + } 265 + } 266 + ``` 267 + 268 + ### Rate Limit Configuration 269 + 270 + ```toml 271 + [rate_limits] 272 + per_did = { requests_per_minute = 60, burst = 10 } 273 + per_ip = { requests_per_second = 10, burst = 50 } 274 + mutations = { addBookToPool = "10/hour", requestBookLoan = "20/hour" } 275 + firehose = { events_per_second = 1000, max_backlog = 10000 } 276 + ``` 277 + 278 + ### Rate Limit Middleware 279 + 280 + ```rust 281 + pub async fn rate_limit_middleware( 282 + ctx: &Context, 283 + operation: &str, 284 + ) -> Result<(), RateLimitError> { 285 + let key = format!("{}:{}", ctx.did, operation); 286 + governor::RateLimiter::direct(&key) 287 + .map_err(|_| RateLimitError::TooManyRequests)?; 288 + 289 + if operation == "requestBookLoan" { 290 + let recent = db.count_recent_loans(&ctx.did, Duration::hours(1)).await?; 291 + if recent > 5 { 292 + return Err(RateLimitError::Custom("Too many loan requests")); 293 + } 294 + } 295 + Ok(()) 296 + } 297 + ``` 298 + 299 + ### Performance Contracts 300 + 301 + | Operation | P95 Latency | Throughput | 302 + |-----------|------------|------------| 303 + | GraphQL query (cached) | <50ms | 1000 req/s | 304 + | GraphQL mutation (ATProto write) | <500ms | 50 req/s | 305 + | Firehose event processing | <100ms/event | 500 events/s | 306 + | DID resolution (cached) | <20ms | 2000 req/s | 307 + 308 + --- 309 + 310 + ## 5. Operational Resilience 311 + 312 + ### Health Check Endpoint 313 + 314 + ```rust 315 + async fn health_check( 316 + db: State<PgPool>, 317 + firehose: State<FirehoseSubscriber>, 318 + pds: State<PdsClient>, 319 + ) -> impl IntoResponse { 320 + let checks = futures::join!( 321 + db.ping(), 322 + firehose.connection_status(), 323 + pds.health_check() 324 + ); 325 + let status = if checks.iter().all(|r| r.is_ok()) { 326 + StatusCode::OK 327 + } else { 328 + StatusCode::SERVICE_UNAVAILABLE 329 + }; 330 + Json(json!({ 331 + "status": if status == StatusCode::OK { "healthy" } else { "degraded" }, 332 + "checks": { 333 + "database": checks.0.is_ok(), 334 + "firehose": checks.1.is_ok(), 335 + "pds_connectivity": checks.2.is_ok(), 336 + } 337 + })).status(status) 338 + } 339 + ``` 340 + 341 + ### Graceful Degradation: Offline-First Write Queue 342 + 343 + ```rust 344 + pub async fn add_book_to_pool_fallback( 345 + ctx: &Context, 346 + input: AddBookInput, 347 + ) -> Result<PoolItem> { 348 + let book = self.resolve_book_metadata(&input).await?; 349 + let local_item = self.db.create_pending_pool_item(&ctx.did, &book).await?; 350 + self.sync_queue.enqueue(SyncJob::PublishPoolItem { 351 + did: ctx.did.clone(), 352 + item_uri: local_item.uri.clone(), 353 + }).await?; 354 + Ok(PoolItem { 355 + sync_status: SyncStatus::Pending, 356 + ..local_item.into() 357 + }) 358 + } 359 + 360 + async fn sync_worker(sync_queue: SyncQueue, pds: PdsClient) { 361 + while let Some(job) = sync_queue.dequeue().await { 362 + match job { 363 + SyncJob::PublishPoolItem { did, item_uri } => { 364 + let result = retry_with_backoff(3, || async { 365 + pds.put_record(&item_uri, &record).await 366 + }).await; 367 + match result { 368 + Ok(_) => db.mark_synced(&item_uri).await, 369 + Err(e) => { 370 + log::error!("Sync failed for {}: {}", item_uri, e); 371 + } 372 + } 373 + } 374 + } 375 + } 376 + } 377 + ``` 378 + 379 + ### Audit Logging 380 + 381 + ```rust 382 + pub struct AuditEvent { 383 + pub timestamp: DateTime<Utc>, 384 + pub actor_did: String, 385 + pub operation: String, 386 + pub target_uri: Option<String>, 387 + pub ip_address: Option<String>, 388 + pub result: OperationResult, 389 + pub error_context: Option<String>, 390 + } 391 + 392 + pub async fn audit_mutation_middleware( 393 + ctx: &Context, 394 + info: &ResolveInfo<'_>, 395 + result: &Result<Value, Error>, 396 + ) { 397 + let event = AuditEvent { 398 + timestamp: Utc::now(), 399 + actor_did: ctx.did.clone(), 400 + operation: info.field_name().to_string(), 401 + target_uri: extract_target_uri(info), 402 + ip_address: ctx.remote_addr.clone(), 403 + result: match result { 404 + Ok(_) => OperationResult::Success, 405 + Err(e) => OperationResult::Failed, 406 + }, 407 + error_context: result.as_ref().err().map(|e| sanitize_error(e)), 408 + }; 409 + tokio::spawn(async move { 410 + audit_logger.log(event).await.ok(); 411 + }); 412 + } 413 + ``` 414 + 415 + ### Observability Stack 416 + 417 + - **Metrics**: Prometheus exporters — request rates, error rates, firehose lag 418 + - **Tracing**: OpenTelemetry spans for GraphQL resolvers + ATProto calls 419 + - **Logging**: Structured JSON with correlation IDs 420 + - **Alerts**: PagerDuty for firehose disconnection >5min, auth failure spikes 421 + 422 + --- 423 + 424 + ## 6. Cover Art: Hybrid Blob Strategy 425 + 426 + ```rust 427 + pub async fn handle_cover_art( 428 + &self, 429 + image_url: String, 430 + user_did: String, 431 + ) -> Result<CoverArtReference> { 432 + let image_bytes = self.fetch_and_validate_image(&image_url).await?; 433 + let blob_ref = self.pds_client 434 + .upload_blob(&user_did, &image_bytes, "image/jpeg") 435 + .await?; 436 + Ok(CoverArtReference::BlobRef(blob_ref)) 437 + } 438 + 439 + async fn resolve_cover_art(&self, cid: String) -> Result<impl IntoResponse> { 440 + if let Ok(blob) = self.pds_client.get_blob(&cid).await { 441 + return Ok(Response::new(Body::from(blob.bytes)) 442 + .header("content-type", blob.mime_type)); 443 + } 444 + if let Some(cached) = self.local_cache.get(&cid).await { 445 + return Ok(Response::new(Body::from(cached.bytes)) 446 + .header("content-type", cached.mime_type)); 447 + } 448 + Err(ApiError::NotFound) 449 + } 450 + ``` 451 + 452 + Resolution order: PDS blob → local cache → 404. 453 + 454 + --- 455 + 456 + ## 7. Implementation Checklist 457 + 458 + ### Security 459 + - [ ] DPoP-bound OAuth 2.0 flow 460 + - [ ] DID document validation per request 461 + - [ ] Lexicon-scoped permission enforcement 462 + - [ ] ATProto signature verification pre-processing 463 + - [ ] Rate limiting per-DID and per-IP 464 + 465 + ### Federation 466 + - [ ] Firehose cursor persistence (idempotent) 467 + - [ ] Multi-relay failover with exponential backoff 468 + - [ ] Backfill via `com.atproto.sync.getRepo` 469 + - [ ] Per-DID event ordering 470 + 471 + ### Data 472 + - [ ] AT-URI canonical identifiers 473 + - [ ] Work-level grouping with edition-specific loans 474 + - [ ] Lost-book state machine with resolution paths 475 + - [ ] Reputation signal propagation via firehose 476 + 477 + ### Performance 478 + - [ ] L1/L2/L3 cache hierarchy 479 + - [ ] Async ATProto write queue 480 + - [ ] Tokio task pool parallelization 481 + - [ ] Cursor-based query pagination 482 + 483 + ### Operations 484 + - [ ] Health check endpoints (DB, firehose, PDS) 485 + - [ ] Graceful degradation on ATProto unavailability 486 + - [ ] Structured audit logging (all mutations) 487 + - [ ] Prometheus + OpenTelemetry instrumentation 488 + 489 + ### Edge Cases 490 + - [ ] DID key rotation → re-auth flow 491 + - [ ] Firehose gap detection → backfill 492 + - [ ] Offline mutation queue → retry with backoff 493 + - [ ] Loan dispute resolution workflow 494 + 495 + --- 496 + 497 + ## 8. Deployment Configuration 498 + 499 + ```toml 500 + [instance] 501 + handle = "library.yourdomain.org" 502 + admin_did = "did:plc:..." 503 + 504 + [atproto] 505 + relay_endpoints = [ 506 + "wss://bsky.network", 507 + "wss://relay.example.org", 508 + ] 509 + pds_discovery_ttl_seconds = 300 510 + 511 + [firehose] 512 + cursor_persistence = "postgresql" 513 + backfill_enabled = true 514 + max_concurrent_backfills = 5 515 + 516 + [cache] 517 + l1_max_entries = 10000 518 + l1_ttl_seconds = 300 519 + redis_cluster_nodes = ["redis-1:6379", "redis-2:6379"] 520 + 521 + [rate_limits] 522 + enabled = true 523 + per_did_requests_per_minute = 60 524 + mutation_burst_limit = 10 525 + 526 + [observability] 527 + prometheus_endpoint = "/metrics" 528 + opentelemetry_exporter = "jaeger" 529 + audit_log_sink = "postgresql" 530 + ``` 531 + 532 + ### Infrastructure Requirements 533 + 534 + - `atproto-subscriber`: separate binary for independent scaling 535 + - Kubernetes liveness/readiness probes → `/health` endpoint 536 + - PostgreSQL WAL archiving for point-in-time cursor recovery 537 + - TLS mutual auth for inter-service communication 538 + 539 + --- 540 + 541 + ## Next Steps 542 + 543 + 1. Implement DPoP authentication middleware 544 + 2. Build firehose subscriber with cursor persistence PoC 545 + 3. Define lexicon schemas in `./lexicons/com/example/library/` 546 + 4. Load-test GraphQL resolvers with federation traffic patterns
+228
humandocs.md
··· 1 + # DeZe — The Decentralized Library: How It Works 2 + 3 + > **One-Line Summary**: DeZe is a community-run book-sharing platform where you own your data, no single company is in charge, and anyone can participate — think of it as “email for lending books.” 4 + 5 + --- 6 + 7 + ## 📬 What is AT Protocol? (The Foundation) 8 + 9 + Imagine the postal system. You can live in any city, use any local post office, but you can still send a letter to anyone in the world. No single post office “owns” the mail system — they all agree on the same rules (addresses, stamps, delivery standards) so the whole thing works together. 10 + 11 + **AT Protocol works the same way, but for your digital life.** 12 + 13 + - **Your Personal Data Server (PDS)** = Your local post office. It holds your letters (data), knows your address (identity), and sends/receives on your behalf. You can pick which post office to use, or even run your own. 14 + - **Your DID (Decentralized Identifier)** = Your permanent mailing address. Even if you move to a different post office, your address stays the same. People can always find you. Think of it as a passport number for the internet — it’s yours forever. 15 + - **Relays / The Firehose** = The central mail-sorting facility. All the post offices send copies of their outgoing mail here, so anyone who needs to know what’s happening across the network can watch the stream. In our library, this is how one community’s book listings become visible to everyone else. 16 + - **Lexicons** = The agreed-upon form templates. Just like the post office has standard forms for different services (registered mail, packages, insurance claims), AT Protocol has “lexicons” — shared definitions for what a “book listing,” “loan request,” or “return confirmation” looks like. This ensures every post office speaks the same language. 17 + - **Records** = The actual letters/forms you fill out and send. When you list a book, request a loan, or confirm a return, you’re creating a record that gets stored at your post office and announced to the network. 18 + 19 + ### Why does this matter for a library? 20 + 21 + | Traditional App (e.g., Goodreads) | DeZe (Decentralized Library) | 22 + |---|---| 23 + | One company owns all your data | **You** own your book lists, loan history, reviews | 24 + | Company shuts down → everything is lost | Your data lives at your PDS — move it anytime | 25 + | Company decides the rules | Communities set their own rules | 26 + | One server = one point of failure | Many servers work together | 27 + | You can’t take your profile elsewhere | Your identity (DID) travels with you | 28 + 29 + --- 30 + 31 + ## 📚 How the Library Works: Step by Step 32 + 33 + ### Step 1: Join the Library 34 + 35 + 1. You create an account on AT Protocol (just like creating an email account — pick a provider or host your own). 36 + 2. You get a **handle** (like `@alice.library.org`) and a permanent **DID** behind the scenes. 37 + 3. You log in to the library using your AT Protocol account — **no new passwords to remember**. The library never stores your password; it just verifies who you are through your PDS (your “post office”). 38 + 39 + ### Step 2: Add a Book to the Pool 40 + 41 + Think of the **pool** as a communal bookshelf in a coffee shop — but spanning the entire internet. 42 + 43 + 1. You tell the library: “I have a copy of *Dune* by Frank Herbert, ISBN 978-0-441-17271-9.” 44 + 2. The system looks up the book details automatically (title, author, cover art, edition info) from public databases like OpenLibrary or Wikidata. 45 + 3. Your book is now listed in the public pool. Anyone on any connected library server can see it’s available. 46 + 4. Behind the scenes: a record is created in **your** PDS (your post office), and the network is notified that a new book is available. 47 + 48 + ### Step 3: Browse & Discover 49 + 50 + - You can search or browse the pool for books you’d like to read. 51 + - The library groups all editions of the same work together. So if you search for “Dune,” you’ll see the hardcover, paperback, audiobook, and every other edition — along with how many copies of each are available across the network. 52 + - Even though different people use different library servers, **everyone’s books show up in the same search** because all the servers share information through the AT Protocol relay (the “mail sorting facility”). 53 + 54 + ### Step 4: Request a Loan 55 + 56 + 1. You find a book you want. You click “Request to Borrow.” 57 + 2. The book’s owner gets a notification (a loan request record is created on the network). 58 + 3. The owner reviews your request and either **approves** or **declines** it. 59 + 4. If approved, a **Loan Agreement** is created — a formal record on both your accounts that says: “Alice is lending *Dune* to Bob, expected return by [date].” 60 + 61 + ### Step 5: The Loan Lifecycle 62 + 63 + A loan goes through clear stages, like checking out a library book with a date-stamped card: 64 + 65 + ``` 66 + 📋 Pending Approval → ✅ Active → 📦 Returned 67 + ↘ ⏰ Overdue → 🔍 See “Lost Book” below 68 + ``` 69 + 70 + - **Pending Approval**: You’ve asked to borrow; the owner hasn’t responded yet. 71 + - **Active**: The loan is confirmed. You have the book. 72 + - **Returned**: You’ve given the book back. The owner confirms receipt. The book goes back into the pool. 73 + - **Overdue**: The expected return date has passed. The system flags this automatically. 74 + 75 + ### Step 6: Return the Book 76 + 77 + 1. You give the book back to the owner (in person, by mail — however you arranged it). 78 + 2. The owner marks the loan as “Returned” in the system. 79 + 3. The book automatically becomes available in the pool again for others to borrow. 80 + 81 + --- 82 + 83 + ## 🔍 What Happens When a Book is Lost? 84 + 85 + Life happens. Books get lost, damaged, or “permanently borrowed.” The system has a clear process for this: 86 + 87 + 1. **The owner reports the book as lost.** Only the owner (lender) can do this — not the borrower, and not the system automatically. 88 + 2. **The owner chooses a resolution:** 89 + - 🤝 **Forgive** — “No worries, it happens.” No penalty. 90 + - 💰 **Compensate** — “Please replace it or make it right.” This affects the borrower’s reputation score. 91 + - 🔎 **Investigate** — “Let’s figure out what happened.” Opens a discussion. 92 + 3. **The borrower can dispute** the claim if they believe the book was returned. This creates a “Disputed” status so both sides can work it out. 93 + 4. **Once resolved**, the loan is permanently closed, and the book is removed from the pool (it’s no longer available to lend). 94 + 95 + This is like a library’s “lost book” policy — clear steps, fair for both sides, with a paper trail. 96 + 97 + --- 98 + 99 + ## ⭐ Trust & Reputation 100 + 101 + Since there’s no central company policing behaviour, the community needs a way to know who’s trustworthy. The library uses a **reputation system**: 102 + 103 + - **Positive signals**: Returning books on time, being a generous lender, getting good reviews from borrowing partners. 104 + - **Negative signals**: Losing books (especially with “Compensate” resolutions), frequently going overdue, disputed loans. 105 + - **Visibility**: Your reputation is visible to anyone considering lending to you or borrowing from you. It’s built up over time from your actual lending history — not just a star rating. 106 + - **Federation-wide**: Because all this data flows through AT Protocol, your reputation travels with you. If you move to a different library server, your track record follows. 107 + 108 + Think of it like an eBay seller rating — but for book lending, and nobody owns the rating system. 109 + 110 + --- 111 + 112 + ## 🌐 Federation: How Multiple Libraries Connect 113 + 114 + “Federation” is a fancy word for a simple idea: **independent groups working together by following shared rules.** 115 + 116 + ### The Coffee Shop Analogy 117 + 118 + Imagine three different coffee shops, each with a community bookshelf: 119 + - ☕ **Bean & Books** in Melbourne 120 + - ☕ **Café Literati** in London 121 + - ☕ **The Reading Room** in Toronto 122 + 123 + Without federation, each bookshelf is isolated. You can only browse and borrow from the one you visit. 124 + 125 + With federation (AT Protocol), these three bookshelves are **connected**: 126 + - A book listed at Bean & Books shows up when someone at Café Literati searches for it. 127 + - Loan agreements work across shops — Alice at Bean & Books can lend to Bob at The Reading Room. 128 + - Each shop still sets its own house rules (opening hours, membership fees, behaviour policies), but the **book-sharing protocol** is the same everywhere. 129 + 130 + ### How does this actually work? 131 + 132 + 1. Each library server runs independently (like each coffee shop is its own business). 133 + 2. When someone adds a book or creates a loan, that event is **announced to the network** via the relay (the “mail sorting facility”). 134 + 3. Every other library server **picks up these announcements** and updates its own catalogue. 135 + 4. The result: a unified, searchable library that spans many independent communities — with no single point of control or failure. 136 + 137 + --- 138 + 139 + ## 🖼️ Book Covers & Images 140 + 141 + When you add a book, the system handles cover art automatically: 142 + 143 + 1. It fetches the cover image from public databases. 144 + 2. The image is stored in **your** PDS (your “post office”) — so you’re not dependent on any external service to keep it available. 145 + 3. When someone views your book listing, the library serves the cover image efficiently, using cached copies to keep things fast. 146 + 147 + This means even if a public book-cover database goes offline, your listings still look complete because the images are stored in the decentralized network. 148 + 149 + --- 150 + 151 + ## 🛡️ Security & Privacy (In Plain Language) 152 + 153 + ### Your Identity is Yours 154 + 155 + - You log in with your AT Protocol identity. The library **never sees or stores your password**. 156 + - Your login is verified through a secure handshake between the library and your PDS — like showing your passport at the border instead of giving them a copy to keep. 157 + - Even if the library server disappears, your identity and data remain safe at your PDS. 158 + 159 + ### Permissions are Specific 160 + 161 + - The library only asks for the permissions it needs: “Can I read your book list?” “Can I create a loan agreement on your behalf?” 162 + - You can revoke access at any time from your PDS, just like revoking an app’s access to your Google account. 163 + 164 + ### Everything is Verified 165 + 166 + - Every action on the network is **cryptographically signed** — like a wax seal on a letter. If someone tries to fake a loan agreement or tamper with your book listing, the system detects it immediately. 167 + - This happens automatically. You don’t need to do anything extra. 168 + 169 + ### Protection Against Abuse 170 + 171 + - The system limits how many actions you can perform in a given time (to prevent spamming or automated abuse). 172 + - Every important action (adding books, creating loans, reporting losses) is logged in an audit trail — not to spy on you, but to provide evidence if there’s ever a dispute. 173 + 174 + --- 175 + 176 + ## 🔄 What Happens When Things Go Wrong? 177 + 178 + ### The network is temporarily down 179 + 180 + If AT Protocol’s network is having issues, the library doesn’t just break. Instead: 181 + 1. Your action (e.g., adding a book) is saved locally with a “pending” status. 182 + 2. A background process keeps trying to sync it to the network. 183 + 3. Once the network is back, everything catches up automatically. 184 + 185 + It’s like writing a letter when the post office is closed — you put it in the outbox, and it gets sent as soon as they reopen. 186 + 187 + ### A library server goes offline 188 + 189 + Because your data lives at **your** PDS (not on the library server), nothing is lost. You can connect to a different library server and pick up where you left off — your books, your loans, your reputation all travel with you. 190 + 191 + ### Data gets out of sync 192 + 193 + The system has built-in mechanisms to detect and fill in gaps. If a library server misses some announcements from the network (like when your phone reconnects after being in airplane mode), it automatically requests the missing updates and gets back in sync. 194 + 195 + --- 196 + 197 + ## 🗺️ Glossary of Terms 198 + 199 + | Term | Plain English | 200 + |------|--------------| 201 + | **AT Protocol** | The shared “postal system” rules that let different library servers talk to each other | 202 + | **PDS (Personal Data Server)** | Your “post office” — where your data is stored and managed | 203 + | **DID (Decentralized Identifier)** | Your permanent internet passport number — yours forever, even if you switch providers | 204 + | **Relay / Firehose** | The “mail sorting facility” that broadcasts network activity so everyone stays up to date | 205 + | **Lexicon** | A shared form template that ensures everyone describes books, loans, etc. the same way | 206 + | **Record** | A single “form” you’ve filled out — a book listing, a loan request, a return confirmation | 207 + | **Pool** | The communal bookshelf — all books that people have made available to lend | 208 + | **Federation** | Independent library servers working together by following the same rules | 209 + | **Loan Agreement** | The formal record of who is lending what to whom, and when it’s due back | 210 + | **Reputation** | Your track record as a borrower and lender, visible across the network | 211 + | **Handle** | Your human-readable username (like `@alice.library.org`) | 212 + | **Work** | A book title that may have many editions (e.g., “Dune” the novel) | 213 + | **Edition** | A specific version of a work (e.g., “Dune, 1st Edition Hardcover, 1965”) | 214 + 215 + --- 216 + 217 + ## 💡 Key Principles 218 + 219 + 1. **You own your data.** Your books, loans, and reputation live in your PDS — not on someone else’s server. 220 + 2. **No single point of failure.** If one library server goes down, the rest keep running. Your data is safe. 221 + 3. **Portable identity.** Switch library servers without losing anything — your DID travels with you. 222 + 4. **Community governance.** Each library community sets its own rules. There’s no central authority deciding what’s allowed. 223 + 5. **Transparent & verifiable.** Every action is signed and logged. Disputes have evidence. Trust is earned, not assumed. 224 + 6. **Works even when the network hiccups.** Actions queue up locally and sync when connectivity is restored. 225 + 226 + --- 227 + 228 + > **In essence**: DeZe is to book lending what email is to messaging — an open, portable, community-driven system where you’re in control. AT Protocol is the invisible plumbing that makes it all work, just like SMTP is the invisible plumbing behind your email.