···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