···11+# CLAUDE.md
22+33+This file provides guidance to Claude Code (claude.ai/code) when working with
44+code in this repository.
55+66+## Commands
77+88+### Testing
99+1010+```bash
1111+# Run all tests across the monorepo
1212+deno test --allow-env
1313+1414+# Run tests for a specific package
1515+deno test packages/crypto/
1616+1717+# Run E2E tests (requires CISTERN_HANDLE and CISTERN_APP_PASSWORD environment variables)
1818+deno test --allow-env --allow-net e2e.test.ts
1919+```
2020+2121+### Lexicon Code Generation
2222+2323+```bash
2424+# Generate TypeScript types from JSON lexicon definitions
2525+cd packages/lexicon
2626+deno task generate
2727+```
2828+2929+This generates types in `packages/lexicon/src/types/` from the JSON schemas in
3030+`packages/lexicon/lexicons/`. Run this after modifying any `.json` files in the
3131+lexicons directory.
3232+3333+### Type Checking
3434+3535+```bash
3636+# Deno executes TypeScript directly - no build step needed
3737+# Check types explicitly with:
3838+deno check <file.ts>
3939+```
4040+4141+## Architecture Overview
4242+4343+Cistern is a **Deno monorepo** implementing a private, encrypted quick-capture
4444+system on AT Protocol. Items are end-to-end encrypted using post-quantum
4545+cryptography and stored temporarily in the user's PDS (Personal Data Server).
4646+4747+### Monorepo Structure
4848+4949+Five packages with clear separation of concerns:
5050+5151+- **`@cistern/crypto`** - Core cryptographic primitives
5252+ (encryption/decryption/keys)
5353+- **`@cistern/lexicon`** - AT Protocol schema definitions (pubkey + item
5454+ records)
5555+- **`@cistern/shared`** - Authentication utilities and common code
5656+- **`@cistern/producer`** - Creates and encrypts items for storage
5757+- **`@cistern/consumer`** - Retrieves, decrypts, and deletes items
5858+5959+Internal imports use the `@cistern/*` namespace defined in each package's
6060+`deno.jsonc`.
6161+6262+### Producer/Consumer Pattern
6363+6464+**Producer** workflow (packages/producer/mod.ts):
6565+6666+1. Select a public key from those registered in the user's PDS
6767+2. Encrypt plaintext using the public key
6868+3. Create an `app.cistern.lexicon.item` record with the encrypted payload
6969+4. Upload to PDS
7070+7171+**Consumer** workflow (packages/consumer/mod.ts):
7272+7373+1. Generate an X-Wing keypair (post-quantum)
7474+2. Upload public key to PDS as `app.cistern.lexicon.pubkey` record
7575+3. Keep private key locally (never uploaded)
7676+4. Retrieve items via **polling** (`listItems()`) or **streaming**
7777+ (`subscribeToItems()`)
7878+5. Decrypt items matching the local keypair
7979+6. Delete items after consumption
8080+8181+### Encryption Architecture
8282+8383+**Algorithm**: `x_wing-xchacha20_poly1305-sha3_512`
8484+8585+The encryption system uses a hybrid approach combining:
8686+8787+- **X-Wing KEM** (Key Encapsulation Mechanism) - post-quantum hybrid combining
8888+ ML-KEM-768 and X25519
8989+- **XChaCha20-Poly1305** - authenticated encryption cipher
9090+- **SHA3-512** - content integrity verification
9191+9292+**Encryption flow** (packages/crypto/src/encrypt.ts):
9393+9494+1. X-Wing encapsulation generates a shared secret from the public key
9595+2. XChaCha20-Poly1305 encrypts the plaintext using the shared secret
9696+3. SHA3-512 hash computed for integrity verification
9797+4. Returns `EncryptedPayload` containing ciphertext, nonce, hash, and metadata
9898+9999+**Decryption flow** (packages/crypto/src/decrypt.ts):
100100+101101+1. X-Wing decapsulation recovers the shared secret using the private key
102102+2. XChaCha20-Poly1305 decrypts the content
103103+3. Integrity verification: check content length and SHA3-512 hash match
104104+4. Returns plaintext or throws error if verification fails
105105+106106+### AT Protocol Integration
107107+108108+Cistern uses two record types in the user's PDS:
109109+110110+**`app.cistern.lexicon.pubkey`**
111111+(packages/lexicon/lexicons/app/cistern/lexicon/pubkey.json):
112112+113113+- Stores public keys with human-readable names
114114+- Referenced by items via AT-URI
115115+- Schema: `{name, algorithm, content, createdAt}`
116116+117117+**`app.cistern.lexicon.item`**
118118+(packages/lexicon/lexicons/app/cistern/lexicon/item.json):
119119+120120+- Stores encrypted items temporarily
121121+- Schema:
122122+ `{tid, ciphertext, nonce, algorithm, pubkey, payload, contentLength, contentHash}`
123123+- The `pubkey` field is an AT-URI reference to the public key record
124124+125125+### Real-time Streaming
126126+127127+The consumer can subscribe to new items via **Jetstream**
128128+(packages/consumer/mod.ts:150-190):
129129+130130+- Connects to Bluesky's Jetstream WebSocket service
131131+- Filters for `app.cistern.lexicon.item` creates matching user DID
132132+- Decrypts items as they arrive in real-time
133133+- Used for instant delivery (e.g., Obsidian plugin waiting for new memos)
134134+135135+### Key Management
136136+137137+**Private keys never leave the consumer's device.** The security model depends
138138+on:
139139+140140+- Private key stored off-protocol (e.g., in an Obsidian vault)
141141+- Public key stored in PDS as a record
142142+- Items encrypted with public key can only be decrypted by matching private key
143143+- Each keypair can have a human-readable name (e.g., "Work Laptop", "Phone")
144144+145145+### Dependencies
146146+147147+**Cryptography** (JSR packages):
148148+149149+- `@noble/post-quantum` - X-Wing KEM implementation
150150+- `@noble/ciphers` - XChaCha20-Poly1305
151151+- `@noble/hashes` - SHA3-512
152152+153153+**AT Protocol** (npm packages):
154154+155155+- `@atcute/client` - RPC client for PDS communication
156156+- `@atcute/jetstream` - Real-time event streaming
157157+- `@atcute/lexicons` - Schema validation
158158+- `@atcute/tid` - Timestamp identifiers
159159+160160+## Key Files and Locations
161161+162162+### Cryptographic Operations
163163+164164+- `packages/crypto/src/keys.ts` - Keypair generation (X-Wing)
165165+- `packages/crypto/src/encrypt.ts` - Encryption logic
166166+- `packages/crypto/src/decrypt.ts` - Decryption + integrity verification
167167+- `packages/crypto/src/*.test.ts` - Crypto unit tests
168168+169169+### Producer Implementation
170170+171171+- `packages/producer/mod.ts` - Main producer class and encryption workflow
172172+173173+### Consumer Implementation
174174+175175+- `packages/consumer/mod.ts` - Keypair management, item retrieval, Jetstream
176176+ subscription
177177+178178+### Authentication
179179+180180+- `packages/shared/produce-requirements.ts` - DID resolution and session
181181+ creation
182182+- Uses Slingshot service for handle → DID resolution
183183+- Creates authenticated RPC client with app password
184184+185185+### Schema Definitions
186186+187187+- `packages/lexicon/lexicons/app/cistern/lexicon/*.json` - AT Protocol record
188188+ schemas
189189+- `packages/lexicon/src/types/` - Generated TypeScript types (run
190190+ `deno task generate` to update)
191191+- `packages/lexicon/lex.config.ts` - Lexicon generator configuration
192192+193193+## Important Patterns
194194+195195+### Error Handling in Decryption
196196+197197+Decryption can fail for multiple reasons (packages/crypto/src/decrypt.ts):
198198+199199+- Wrong private key (decapsulation fails)
200200+- Corrupted ciphertext (authentication fails)
201201+- Length mismatch (integrity check fails)
202202+- Hash mismatch (integrity check fails)
203203+204204+Always wrap decrypt calls in try-catch and handle gracefully.
205205+206206+### Pagination in Consumer
207207+208208+`listItems()` returns an async generator that handles pagination automatically.
209209+It yields decrypted items and internally manages cursors. Consumers should
210210+iterate with `for await` loops.
211211+212212+### Resource URIs
213213+214214+AT Protocol uses AT-URIs to reference records: `at://<did>/<collection>/<rkey>`
215215+216216+The consumer caches the public key's AT-URI with the local keypair to filter
217217+which items it can decrypt.
218218+219219+## Testing
220220+221221+### Unit Tests
222222+223223+Each package contains unit tests following these conventions:
224224+- Test files use `.test.ts` suffix
225225+- Use `@std/expect` for assertions
226226+- Mock external dependencies (RPC clients, credentials)
227227+- Test both success and error paths
228228+229229+**Test locations:**
230230+- `packages/crypto/src/*.test.ts` - Cryptographic operations
231231+- `packages/consumer/mod.test.ts` - Consumer functionality
232232+- `packages/producer/mod.test.ts` - Producer functionality
233233+234234+### End-to-End Tests
235235+236236+`e2e.test.ts` contains integration tests that use real AT Protocol credentials:
237237+- Requires `CISTERN_HANDLE` and `CISTERN_APP_PASSWORD` environment variables
238238+- Tests full workflow: keypair generation, encryption, decryption, deletion
239239+- Uses Deno test steps to segment each phase
240240+- Automatically skipped if environment variables are not set
241241+- Cleans up all test data after execution