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