Skip to content

Commit cb4e0e6

Browse files
committed
docs(plans): add migration plan for canonical encryption config
Add implementation plan for migrating proxy to use CanonicalEncryptionConfig from the cipherstash-config crate.
1 parent 30aafc6 commit cb4e0e6

1 file changed

Lines changed: 259 additions & 0 deletions

File tree

Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
# Migrate Proxy to Canonical Encryption Config — Implementation Plan
2+
3+
> **For Claude:** REQUIRED SUB-SKILL: Use cipherpowers:executing-plans to implement this plan task-by-task.
4+
5+
**Goal:** Remove the proxy's local `CastAs` enum and `ColumnEncryptionConfig` parser, replacing them with `CanonicalEncryptionConfig` from the `cipherstash-config` crate.
6+
7+
**Architecture:** The proxy currently has its own JSON config parser in `encrypt_config/config.rs` (~490 lines) that duplicates what `cipherstash-config` provides. We replace it with the canonical parser, keeping only the `EncryptConfig` wrapper and `EncryptConfigManager` which handle proxy-specific concerns (Arc-wrapped config, background reload).
8+
9+
**Tech Stack:** Rust, serde, serde_json, cipherstash-config
10+
11+
**Prerequisite:** The canonical config work in `cipherstash-suite` (CIP-2871) must be completed first — specifically, `CanonicalEncryptionConfig`, `PlaintextType`, `Identifier`, and `into_config_map()` must exist in `cipherstash-config`.
12+
13+
**Design doc:** `~/cipherstash/cipherstash-suite/docs/plans/2026-04-01-canonical-encryption-config-design.md`
14+
15+
---
16+
17+
### Task 1: Add `cipherstash-config` dependency
18+
19+
**Files:**
20+
- Modify: `Cargo.toml` (workspace root)
21+
- Modify: `packages/cipherstash-proxy/Cargo.toml`
22+
23+
**Step 1: Add to workspace dependencies**
24+
25+
In the root `Cargo.toml`, add `cipherstash-config` to `[workspace.dependencies]`. Match the version/source used for `cipherstash-client`.
26+
27+
**Step 2: Add to cipherstash-proxy package**
28+
29+
In `packages/cipherstash-proxy/Cargo.toml`, add:
30+
31+
```toml
32+
cipherstash-config = { workspace = true }
33+
```
34+
35+
**Step 3: Verify it compiles**
36+
37+
Run: `cargo check -p cipherstash-proxy`
38+
Expected: Clean build
39+
40+
**Step 4: Commit**
41+
42+
```bash
43+
git add Cargo.toml Cargo.lock packages/cipherstash-proxy/Cargo.toml
44+
git commit --no-gpg-sign -m "chore: add cipherstash-config dependency to cipherstash-proxy"
45+
```
46+
47+
---
48+
49+
### Task 2: Replace `ColumnEncryptionConfig` with `CanonicalEncryptionConfig` in the manager
50+
51+
**Files:**
52+
- Modify: `packages/cipherstash-proxy/src/proxy/encrypt_config/manager.rs`
53+
54+
**Step 1: Update imports**
55+
56+
Replace the import of local `ColumnEncryptionConfig`:
57+
58+
```rust
59+
// Before
60+
use super::config::ColumnEncryptionConfig;
61+
62+
// After
63+
use cipherstash_config::CanonicalEncryptionConfig;
64+
```
65+
66+
**Step 2: Update `load_encrypt_config` function**
67+
68+
The function currently does (around line 216):
69+
70+
```rust
71+
let encrypt_config: ColumnEncryptionConfig = serde_json::from_value(json_value)?;
72+
let encrypt_config = EncryptConfig::new_from_config(encrypt_config.into_config_map());
73+
```
74+
75+
Change to:
76+
77+
```rust
78+
let encrypt_config: CanonicalEncryptionConfig = serde_json::from_value(json_value)
79+
.map_err(|e| /* map to existing error type */)?;
80+
let config_map = encrypt_config.into_config_map()
81+
.map_err(|e| /* map ConfigError to proxy Error */)?;
82+
let encrypt_config = EncryptConfig::new_from_config(config_map);
83+
```
84+
85+
Note: The canonical `into_config_map()` returns `Result<HashMap<Identifier, ColumnConfig>, ConfigError>` (fallible, with validation) while the proxy's was infallible. You'll need to handle the `Result` — map `ConfigError` to the proxy's error type.
86+
87+
Also note: The canonical `Identifier` is from `cipherstash_config::Identifier`, not `cipherstash_client::eql::Identifier`. Check that `EncryptConfig::new_from_config` and `EncryptConfig::get_column_config` use the same `Identifier` type. If they differ, update `EncryptConfig` to use the canonical `Identifier`.
88+
89+
**Step 3: Run tests**
90+
91+
Run: `cargo test -p cipherstash-proxy --lib -- encrypt_config`
92+
Expected: All pass
93+
94+
**Step 4: Commit**
95+
96+
```bash
97+
git add packages/cipherstash-proxy/src/proxy/encrypt_config/manager.rs
98+
git commit --no-gpg-sign -m "refactor: use CanonicalEncryptionConfig in EncryptConfigManager"
99+
```
100+
101+
---
102+
103+
### Task 3: Remove local config types
104+
105+
**Files:**
106+
- Modify: `packages/cipherstash-proxy/src/proxy/encrypt_config/config.rs`
107+
108+
**Step 1: Delete local types**
109+
110+
Remove the following from `config.rs`:
111+
- `ColumnEncryptionConfig` struct
112+
- `Tables` struct and its `IntoIterator` impl
113+
- `Table` struct and its `IntoIterator` impl
114+
- `Column` struct
115+
- `CastAs` enum
116+
- `From<CastAs> for ColumnType` impl
117+
- `OreIndexOpts`, `MatchIndexOpts`, `SteVecIndexOpts`, `UniqueIndexOpts` structs
118+
- `Indexes` struct
119+
- `FromStr for ColumnEncryptionConfig` impl
120+
- `ColumnEncryptionConfig::into_config_map` method
121+
- `Column::into_column_config` method
122+
- All default functions (`default_tokenizer`, `default_k`, `default_m`, `default_array_index_mode`)
123+
124+
This should remove ~200 lines of code. What remains in `config.rs` (if anything) depends on whether the proxy has any config types not covered by the canonical module.
125+
126+
**Step 2: Update `mod.rs` if needed**
127+
128+
If `config.rs` is now empty or only has tests, update `packages/cipherstash-proxy/src/proxy/encrypt_config/mod.rs` accordingly.
129+
130+
**Step 3: Run tests**
131+
132+
Run: `cargo test -p cipherstash-proxy --lib -- encrypt_config`
133+
Expected: All pass
134+
135+
Run: `cargo clippy --no-deps --tests --all-features --all-targets -p cipherstash-proxy -- -D warnings`
136+
Expected: No warnings
137+
138+
**Step 4: Commit**
139+
140+
```bash
141+
git add packages/cipherstash-proxy/src/proxy/encrypt_config/
142+
git commit --no-gpg-sign -m "refactor: remove local CastAs and ColumnEncryptionConfig, use canonical types"
143+
```
144+
145+
---
146+
147+
### Task 4: Update tests to use canonical types
148+
149+
**Files:**
150+
- Modify: `packages/cipherstash-proxy/src/proxy/encrypt_config/config.rs` (test module)
151+
152+
**Step 1: Migrate tests**
153+
154+
The existing tests (lines 210-489) test JSON parsing of the local types. Rewrite them to test via `CanonicalEncryptionConfig` and `into_config_map()`. Key tests to preserve:
155+
156+
- `column_with_empty_options_gets_defaults` — empty column defaults to `Text` with no indexes
157+
- `can_parse_column_with_cast_as``"cast_as": "int"` parses correctly
158+
- `can_parse_ore_index` — ORE index deserializes
159+
- `can_parse_unique_index_with_token_filter` — unique with downcase filter
160+
- `can_parse_match_index_with_defaults` — match index gets k=6, m=2048, Standard tokenizer
161+
- `can_parse_match_index_with_all_opts_set` — custom match options
162+
- `can_parse_ste_vec_index` — STE vec with prefix and array_index_mode
163+
164+
Each test should:
165+
1. Build JSON with `serde_json::json!`
166+
2. Deserialize to `CanonicalEncryptionConfig`
167+
3. Call `into_config_map()`
168+
4. Assert on the resulting `ColumnConfig`
169+
170+
Example:
171+
172+
```rust
173+
#[test]
174+
fn column_with_empty_options_gets_defaults() {
175+
let json = json!({
176+
"v": 1,
177+
"tables": {
178+
"users": {
179+
"email": {}
180+
}
181+
}
182+
});
183+
184+
let config: CanonicalEncryptionConfig = serde_json::from_value(json).unwrap();
185+
let map = config.into_config_map().unwrap();
186+
187+
let id = Identifier::new("users", "email");
188+
let col = map.get(&id).unwrap();
189+
assert_eq!(col.cast_type, ColumnType::Text);
190+
assert!(col.indexes.is_empty());
191+
}
192+
```
193+
194+
Add a backwards-compat test:
195+
196+
```rust
197+
#[test]
198+
fn it_accepts_old_cast_as_jsonb() {
199+
let json = json!({
200+
"v": 1,
201+
"tables": {
202+
"events": {
203+
"data": {
204+
"cast_as": "jsonb",
205+
"indexes": {
206+
"ste_vec": { "prefix": "test" }
207+
}
208+
}
209+
}
210+
}
211+
});
212+
213+
let config: CanonicalEncryptionConfig = serde_json::from_value(json).unwrap();
214+
let map = config.into_config_map().unwrap();
215+
let id = Identifier::new("events", "data");
216+
let col = map.get(&id).unwrap();
217+
assert_eq!(col.cast_type, ColumnType::Json);
218+
}
219+
```
220+
221+
**Step 2: Run tests**
222+
223+
Run: `cargo test -p cipherstash-proxy --lib -- encrypt_config`
224+
Expected: All pass
225+
226+
**Step 3: Commit**
227+
228+
```bash
229+
git add packages/cipherstash-proxy/src/proxy/encrypt_config/
230+
git commit --no-gpg-sign -m "test: migrate encrypt_config tests to use canonical types"
231+
```
232+
233+
---
234+
235+
### Task 5: Full build and test verification
236+
237+
**Files:** None (verification only)
238+
239+
**Step 1: Workspace clippy**
240+
241+
Run: `cargo clippy --no-deps --tests --all-features --all-targets --workspace -- -D warnings`
242+
Expected: No warnings
243+
244+
**Step 2: Unit tests**
245+
246+
Run: `cargo test --workspace --all-features --lib`
247+
Expected: All pass
248+
249+
**Step 3: Integration tests (if environment available)**
250+
251+
Run: `mise run test:local:integration` (requires PostgreSQL running)
252+
Expected: All pass
253+
254+
**Step 4: If any failures, fix and commit**
255+
256+
```bash
257+
git add -u
258+
git commit --no-gpg-sign -m "fix: resolve build issues from canonical config migration"
259+
```

0 commit comments

Comments
 (0)