Skip to main content

max / synckit

Correct the synckit-client docs and three comment nits README crate-structure table described a flat client.rs against the real client/ (11 files) + store/ (11 files) tree, and omitted conflict.rs, identity.rs, ids.rs, and oauth.rs entirely. Rewrite it around the two-layer split. integration_patterns.md Key Files table pointed at MNW/shared/synckit-client, which moved to ~/Code/synckit in July. Five more rows in the same table were also stale and are corrected against the tree: GO moved sync_service.rs to syncstore/, BB and AF sync services are directories, and both server paths are now directories. Comment nits: explain why the write! result is discarded in hash_row_id (it is infallible) and why the test-only rustls provider install discards its Err (a provider already being installed is the wanted outcome). The vacuous doc on config() is replaced rather than deleted, since the crate sets warn(missing_docs) and it is public API.
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-07-27 21:02 UTC
Signed with PGP, not checked
Commit: 363928b77f3c4ac9d36e7c390eb9152a37e303ec
Parent: 52a635a
4 files changed, +37 insertions, -25 deletions
@@ -6,12 +6,12 @@
6 6
7 7 ## Features
8 8
9 - - **E2E encryption** -- XChaCha20-Poly1305 with Argon2id key derivation (64 MB, 3 iterations)
10 - - **OS keychain integration** -- master key cached in macOS Keychain, Linux secret-service, or Windows Credential Manager
11 - - **Blob encryption** -- binary files encrypted with fixed 40-byte overhead (no base64 expansion)
12 - - **Retry with backoff** -- transient failures (network, 5xx, 429) retried up to 3 times with exponential delay
13 - - **OAuth2 PKCE** -- browser-based auth flow alongside email/password
14 - - **Token expiry detection** -- client-side JWT check with 30-second buffer
9 + - **E2E encryption**: XChaCha20-Poly1305 with Argon2id key derivation (64 MB, 3 iterations)
10 + - **OS keychain integration**: master key cached in macOS Keychain, Linux secret-service, or Windows Credential Manager
11 + - **Blob encryption**: binary files encrypted with fixed 40-byte overhead (no base64 expansion)
12 + - **Retry with backoff**: transient failures (network, 5xx, 429) retried up to 3 times with exponential delay
13 + - **OAuth2 PKCE**: browser-based auth flow alongside email/password
14 + - **Token expiry detection**: client-side JWT check with 30-second buffer
15 15
16 16 ## Quick Start
17 17
@@ -51,13 +51,22 @@
51 51
52 52 ## Crate Structure
53 53
54 - | File | Role |
54 + Two layers. `client/` is the HTTP transport and encryption boundary; `store/` is the
55 + higher-level engine that absorbs the SQLite plumbing a consuming app would otherwise
56 + write itself. An app can use either.
57 +
58 + | Path | Role |
55 59 |------|------|
56 60 | `lib.rs` | Crate root, re-exports, doc example |
57 - | `client.rs` | `SyncKitClient` -- HTTP methods, retry logic, token expiry detection |
61 + | `client/` | `SyncKitClient`: HTTP transport and the high-level API, with transparent end-to-end encryption. Split across `auth`, `blob`, `encryption`, `groups`, `helpers`, `ota`, `rotation`, `subscribe`, `subscription`, `sync` |
62 + | `store/` | `SyncStore`, the syncable-store engine: `apply`, `blob`, `config`, `db`, `facade`, `hlc`, `migrate`, `scheduler`, `schema`, `sync` |
58 63 | `crypto.rs` | Key derivation (Argon2id), key wrapping, per-entry and per-blob encrypt/decrypt |
59 - | `error.rs` | `SyncKitError` enum (10 variants: HTTP, server, JSON, crypto, keychain, auth) |
64 + | `identity.rs` | Group identity keys and Group Content Key grants, the crate's one asymmetric layer |
65 + | `conflict.rs` | Client-side conflict detection and resolution |
66 + | `oauth.rs` | OAuth2 PKCE helpers (RFC 7636, S256) for the MNW authorize/token flow |
60 67 | `keystore.rs` | OS keychain read/write/delete, feature-gated with no-op stubs |
68 + | `ids.rs` | Strongly-typed identifier newtypes over `Uuid` |
69 + | `error.rs` | `SyncKitError` enum (HTTP, server, JSON, crypto, keychain, auth) |
61 70 | `types.rs` | Wire protocol types (`ChangeEntry`, `ChangeOp`, `Device`, `SyncStatus`) |
62 71
63 72 ## Feature Flags
@@ -68,11 +77,11 @@
68 77
69 78 ## Security Properties
70 79
71 - - **Server-zero-knowledge** -- the server never receives the plaintext master key or user data
72 - - **Key zeroization** -- volatile writes clear the master key from memory on drop
73 - - **Random salt per wrap** -- re-wrapping with the same password produces a different envelope
74 - - **Minimum ciphertext validation** -- decryption rejects inputs shorter than 40 bytes (24-byte nonce + 16-byte tag)
75 - - **No key material in logs** -- tracing events never include key bytes or ciphertext
80 + - **Server-zero-knowledge**: the server never receives the plaintext master key or user data
81 + - **Key zeroization**: volatile writes clear the master key from memory on drop
82 + - **Random salt per wrap**: re-wrapping with the same password produces a different envelope
83 + - **Minimum ciphertext validation**: decryption rejects inputs shorter than 40 bytes (24-byte nonce + 16-byte tag)
84 + - **No key material in logs**: tracing events never include key bytes or ciphertext
76 85
77 86 ## License
78 87
@@ -296,12 +296,12 @@
296 296
297 297 | What | Where |
298 298 |------|-------|
299 - | SDK source | `MNW/shared/synckit-client/src/` |
300 - | SDK auth | `MNW/shared/synckit-client/src/client/auth.rs` |
301 - | SDK push/pull | `MNW/shared/synckit-client/src/client/sync.rs` |
302 - | SDK encryption | `MNW/shared/synckit-client/src/crypto.rs` |
303 - | GO sync service | `Apps/goingson/src-tauri/src/sync_service.rs` |
304 - | BB sync service | `Apps/balanced_breakfast/src-tauri/src/sync_service.rs` |
305 - | AF sync service | `Apps/audiofiles/crates/audiofiles-sync/src/service.rs` |
306 - | Server endpoints | `MNW/server/src/routes/synckit.rs` |
307 - | Server DB | `MNW/server/src/db/synckit.rs` |
299 + | SDK source | `synckit/synckit-client/src/` |
300 + | SDK auth | `synckit/synckit-client/src/client/auth.rs` |
301 + | SDK push/pull | `synckit/synckit-client/src/client/sync.rs` |
302 + | SDK encryption | `synckit/synckit-client/src/crypto.rs` |
303 + | GO sync | `Apps/goingson/src-tauri/src/syncstore/` |
304 + | BB sync service | `Apps/balanced_breakfast/src-tauri/src/sync_service/` |
305 + | AF sync service | `Apps/audiofiles/crates/audiofiles-sync/src/service/` |
306 + | Server endpoints | `MNW/server/src/routes/synckit/` |
307 + | Server DB | `MNW/server/src/db/synckit/` and `db/models/synckit.rs` |
@@ -335,6 +335,7 @@
335 335 {
336 336 static PROVIDER: std::sync::Once = std::sync::Once::new();
337 337 PROVIDER.call_once(|| {
338 + // Err means a provider is already installed, which is the outcome we want.
338 339 let _ = rustls::crypto::ring::default_provider().install_default();
339 340 });
340 341 }
@@ -401,7 +402,8 @@
401 402 }
402 403 }
403 404
404 - /// Returns the client configuration.
405 + /// The server URL and API key this client was built with. Both are fixed at
406 + /// construction; changing either means building a new client.
405 407 pub fn config(&self) -> &SyncKitConfig {
406 408 &self.config
407 409 }
@@ -177,9 +177,10 @@
177 177 hasher.update(b":");
178 178 hasher.update(key.as_bytes());
179 179 let digest = hasher.finalize();
180 + use std::fmt::Write;
180 181 let mut hex = String::with_capacity(64);
181 182 for byte in digest {
182 - use std::fmt::Write;
183 + // fmt::Write for String is infallible; the capacity above covers all 64 chars.
183 184 let _ = write!(hex, "{byte:02x}");
184 185 }
185 186 hex