+241
CLAUDE.md
+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