Encrypted, ephemeral, private memos on atproto

chore: replace Claude with Letta

graham.systems 0d9abbce d81141fb

verified
Changed files
+9 -241
.letta
+6
.letta/settings.json
··· 1 + { 2 + "localSharedBlockIds": { 3 + "project": "block-19ad1e80-37cd-4413-8f73-e1dddbb89119", 4 + "skills": "block-25b184a7-e851-42d4-bf5a-9422337e7803" 5 + } 6 + }
+3
.letta/settings.local.json
··· 1 + { 2 + "lastAgent": "agent-2470875d-24da-4bab-8670-293b3cbcf3cf" 3 + }
-241
CLAUDE.md
··· 1 - # CLAUDE.md 2 - 3 - This file provides guidance to Claude Code (claude.ai/code) when working with 4 - code in this repository. 5 - 6 - ## Commands 7 - 8 - ### Testing 9 - 10 - ```bash 11 - # Run all tests across the monorepo 12 - deno test --allow-env 13 - 14 - # Run tests for a specific package 15 - deno test packages/crypto/ 16 - 17 - # Run E2E tests (requires CISTERN_HANDLE and CISTERN_APP_PASSWORD environment variables) 18 - deno test --allow-env --allow-net e2e.test.ts 19 - ``` 20 - 21 - ### Lexicon Code Generation 22 - 23 - ```bash 24 - # Generate TypeScript types from JSON lexicon definitions 25 - cd packages/lexicon 26 - deno task generate 27 - ``` 28 - 29 - This generates types in `packages/lexicon/src/types/` from the JSON schemas in 30 - `packages/lexicon/lexicons/`. Run this after modifying any `.json` files in the 31 - lexicons directory. 32 - 33 - ### Type Checking 34 - 35 - ```bash 36 - # Deno executes TypeScript directly - no build step needed 37 - # Check types explicitly with: 38 - deno check <file.ts> 39 - ``` 40 - 41 - ## Architecture Overview 42 - 43 - Cistern is a **Deno monorepo** implementing a private, encrypted quick-capture 44 - system on AT Protocol. Items are end-to-end encrypted using post-quantum 45 - cryptography and stored temporarily in the user's PDS (Personal Data Server). 46 - 47 - ### Monorepo Structure 48 - 49 - Five packages with clear separation of concerns: 50 - 51 - - **`@cistern/crypto`** - Core cryptographic primitives 52 - (encryption/decryption/keys) 53 - - **`@cistern/lexicon`** - AT Protocol schema definitions (pubkey + item 54 - records) 55 - - **`@cistern/shared`** - Authentication utilities and common code 56 - - **`@cistern/producer`** - Creates and encrypts items for storage 57 - - **`@cistern/consumer`** - Retrieves, decrypts, and deletes items 58 - 59 - Internal imports use the `@cistern/*` namespace defined in each package's 60 - `deno.jsonc`. 61 - 62 - ### Producer/Consumer Pattern 63 - 64 - **Producer** workflow (packages/producer/mod.ts): 65 - 66 - 1. Select a public key from those registered in the user's PDS 67 - 2. Encrypt plaintext using the public key 68 - 3. Create an `app.cistern.lexicon.item` record with the encrypted payload 69 - 4. Upload to PDS 70 - 71 - **Consumer** workflow (packages/consumer/mod.ts): 72 - 73 - 1. Generate an X-Wing keypair (post-quantum) 74 - 2. Upload public key to PDS as `app.cistern.lexicon.pubkey` record 75 - 3. Keep private key locally (never uploaded) 76 - 4. Retrieve items via **polling** (`listItems()`) or **streaming** 77 - (`subscribeToItems()`) 78 - 5. Decrypt items matching the local keypair 79 - 6. Delete items after consumption 80 - 81 - ### Encryption Architecture 82 - 83 - **Algorithm**: `x_wing-xchacha20_poly1305-sha3_512` 84 - 85 - The encryption system uses a hybrid approach combining: 86 - 87 - - **X-Wing KEM** (Key Encapsulation Mechanism) - post-quantum hybrid combining 88 - ML-KEM-768 and X25519 89 - - **XChaCha20-Poly1305** - authenticated encryption cipher 90 - - **SHA3-512** - content integrity verification 91 - 92 - **Encryption flow** (packages/crypto/src/encrypt.ts): 93 - 94 - 1. X-Wing encapsulation generates a shared secret from the public key 95 - 2. XChaCha20-Poly1305 encrypts the plaintext using the shared secret 96 - 3. SHA3-512 hash computed for integrity verification 97 - 4. Returns `EncryptedPayload` containing ciphertext, nonce, hash, and metadata 98 - 99 - **Decryption flow** (packages/crypto/src/decrypt.ts): 100 - 101 - 1. X-Wing decapsulation recovers the shared secret using the private key 102 - 2. XChaCha20-Poly1305 decrypts the content 103 - 3. Integrity verification: check content length and SHA3-512 hash match 104 - 4. Returns plaintext or throws error if verification fails 105 - 106 - ### AT Protocol Integration 107 - 108 - Cistern uses two record types in the user's PDS: 109 - 110 - **`app.cistern.lexicon.pubkey`** 111 - (packages/lexicon/lexicons/app/cistern/lexicon/pubkey.json): 112 - 113 - - Stores public keys with human-readable names 114 - - Referenced by items via AT-URI 115 - - Schema: `{name, algorithm, content, createdAt}` 116 - 117 - **`app.cistern.lexicon.item`** 118 - (packages/lexicon/lexicons/app/cistern/lexicon/item.json): 119 - 120 - - Stores encrypted items temporarily 121 - - Schema: 122 - `{tid, ciphertext, nonce, algorithm, pubkey, payload, contentLength, contentHash}` 123 - - The `pubkey` field is an AT-URI reference to the public key record 124 - 125 - ### Real-time Streaming 126 - 127 - The consumer can subscribe to new items via **Jetstream** 128 - (packages/consumer/mod.ts:150-190): 129 - 130 - - Connects to Bluesky's Jetstream WebSocket service 131 - - Filters for `app.cistern.lexicon.item` creates matching user DID 132 - - Decrypts items as they arrive in real-time 133 - - Used for instant delivery (e.g., Obsidian plugin waiting for new memos) 134 - 135 - ### Key Management 136 - 137 - **Private keys never leave the consumer's device.** The security model depends 138 - on: 139 - 140 - - Private key stored off-protocol (e.g., in an Obsidian vault) 141 - - Public key stored in PDS as a record 142 - - Items encrypted with public key can only be decrypted by matching private key 143 - - Each keypair can have a human-readable name (e.g., "Work Laptop", "Phone") 144 - 145 - ### Dependencies 146 - 147 - **Cryptography** (JSR packages): 148 - 149 - - `@noble/post-quantum` - X-Wing KEM implementation 150 - - `@noble/ciphers` - XChaCha20-Poly1305 151 - - `@noble/hashes` - SHA3-512 152 - 153 - **AT Protocol** (npm packages): 154 - 155 - - `@atcute/client` - RPC client for PDS communication 156 - - `@atcute/jetstream` - Real-time event streaming 157 - - `@atcute/lexicons` - Schema validation 158 - - `@atcute/tid` - Timestamp identifiers 159 - 160 - ## Key Files and Locations 161 - 162 - ### Cryptographic Operations 163 - 164 - - `packages/crypto/src/keys.ts` - Keypair generation (X-Wing) 165 - - `packages/crypto/src/encrypt.ts` - Encryption logic 166 - - `packages/crypto/src/decrypt.ts` - Decryption + integrity verification 167 - - `packages/crypto/src/*.test.ts` - Crypto unit tests 168 - 169 - ### Producer Implementation 170 - 171 - - `packages/producer/mod.ts` - Main producer class and encryption workflow 172 - 173 - ### Consumer Implementation 174 - 175 - - `packages/consumer/mod.ts` - Keypair management, item retrieval, Jetstream 176 - subscription 177 - 178 - ### Authentication 179 - 180 - - `packages/shared/produce-requirements.ts` - DID resolution and session 181 - creation 182 - - Uses Slingshot service for handle → DID resolution 183 - - Creates authenticated RPC client with app password 184 - 185 - ### Schema Definitions 186 - 187 - - `packages/lexicon/lexicons/app/cistern/lexicon/*.json` - AT Protocol record 188 - schemas 189 - - `packages/lexicon/src/types/` - Generated TypeScript types (run 190 - `deno task generate` to update) 191 - - `packages/lexicon/lex.config.ts` - Lexicon generator configuration 192 - 193 - ## Important Patterns 194 - 195 - ### Error Handling in Decryption 196 - 197 - Decryption can fail for multiple reasons (packages/crypto/src/decrypt.ts): 198 - 199 - - Wrong private key (decapsulation fails) 200 - - Corrupted ciphertext (authentication fails) 201 - - Length mismatch (integrity check fails) 202 - - Hash mismatch (integrity check fails) 203 - 204 - Always wrap decrypt calls in try-catch and handle gracefully. 205 - 206 - ### Pagination in Consumer 207 - 208 - `listItems()` returns an async generator that handles pagination automatically. 209 - It yields decrypted items and internally manages cursors. Consumers should 210 - iterate with `for await` loops. 211 - 212 - ### Resource URIs 213 - 214 - AT Protocol uses AT-URIs to reference records: `at://<did>/<collection>/<rkey>` 215 - 216 - The consumer caches the public key's AT-URI with the local keypair to filter 217 - which items it can decrypt. 218 - 219 - ## Testing 220 - 221 - ### Unit Tests 222 - 223 - Each package contains unit tests following these conventions: 224 - - Test files use `.test.ts` suffix 225 - - Use `@std/expect` for assertions 226 - - Mock external dependencies (RPC clients, credentials) 227 - - Test both success and error paths 228 - 229 - **Test locations:** 230 - - `packages/crypto/src/*.test.ts` - Cryptographic operations 231 - - `packages/consumer/mod.test.ts` - Consumer functionality 232 - - `packages/producer/mod.test.ts` - Producer functionality 233 - 234 - ### End-to-End Tests 235 - 236 - `e2e.test.ts` contains integration tests that use real AT Protocol credentials: 237 - - Requires `CISTERN_HANDLE` and `CISTERN_APP_PASSWORD` environment variables 238 - - Tests full workflow: keypair generation, encryption, decryption, deletion 239 - - Uses Deno test steps to segment each phase 240 - - Automatically skipped if environment variables are not set 241 - - Cleans up all test data after execution