Skip to content

Commit 14bce5e

Browse files
committed
wip(spec,driver-turso): retire turso encryptionKey
Claude-Session: https://claude.ai/code/session_01B2EeooonSXrANpkpgUSfFZ Co-authored-by: Claude <noreply@anthropic.com>
1 parent abc25a3 commit 14bce5e

14 files changed

Lines changed: 411 additions & 354 deletions

File tree

‎packages/drivers/driver-turso/README.md‎

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -250,16 +250,13 @@ that nothing would honour, each with the message `@objectstack/spec`'s
250250
would be ignored. Drop `mode` for an embedded replica, or drop `syncUrl` (and
251251
`sync`) for a plain local database.
252252

253-
It refuses (`VALIDATION_ERROR` / 400) a non-empty `encryptionKey` on a local
254-
database or an embedded replica too, in the same message the spec contract
255-
gives at authoring. The driver reads and writes that file through
256-
better-sqlite3, which has no encryption codec, so the key would encrypt nothing
257-
and the file would be written in plaintext that any SQLite reader opens without
258-
it. Drop `encryptionKey`, and put the file on an encrypted disk or volume if it
259-
must be encrypted at rest, or use a remote Turso database, whose storage
260-
encryption is the server's to configure. A `file:` url or `:memory:` under a
261-
forced `mode: 'remote'` is refused the same way: the libSQL client the driver
262-
builds for that url is handed no key.
253+
It refuses (`VALIDATION_ERROR` / 400) any `encryptionKey`, in every mode, in
254+
the same message the spec contract gives at authoring: the key was removed in
255+
18 because it never encrypted anything. A local or replica file is read and
256+
written through better-sqlite3, which has no encryption codec, and the driver
257+
never handed the key to a remote database. Delete the key; put a local file on
258+
an encrypted disk or volume if it must be encrypted at rest, or use a remote
259+
Turso database, whose storage encryption the server configures.
263260

264261
You can also force a specific mode:
265262

@@ -337,13 +334,6 @@ interface TursoDriverConfig {
337334
/** JWT auth token for the remote Turso database */
338335
authToken?: string;
339336

340-
/**
341-
* Encrypts nothing, in any mode: refused in local and replica modes
342-
* (VALIDATION_ERROR / 400), because the local engine cannot encrypt the file,
343-
* and beside a file: url or :memory: under a forced mode: 'remote'.
344-
*/
345-
encryptionKey?: string;
346-
347337
/**
348338
* Maximum concurrent requests to the remote database.
349339
* Effective in replica and remote modes.

‎packages/drivers/driver-turso/src/spec/turso.zod.ts‎

Lines changed: 34 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,24 @@ const WASM_RETIRED =
8181
+ 'the remote arm (`libsql://` / `https://`), which needs none. '
8282
+ 'Run `os migrate meta --from 17` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand.';
8383

84+
/**
85+
* The prescription the REMOVED `encryptionKey` raises (#25050, ADR-0049
86+
* enforce-or-remove). Unlike the three above, it is not this package's own
87+
* wording: it is `@objectstack/spec`'s `TursoConfigSchema` tombstone text, byte
88+
* for byte, and the constructor in `../turso-driver.ts` refuses the key in it
89+
* too, because the one key is judged at three places and an author must meet
90+
* one prescription at all of them. `turso-config-constructor-parity.test.ts`
91+
* holds the three copies equal.
92+
*/
93+
const ENCRYPTION_KEY_RETIRED =
94+
'`turso config.encryptionKey` was removed in @objectstack/spec 18 and @objectstack/driver-turso 18 '
95+
+ '(ADR-0049) — it never encrypted anything, in any mode: a local or embedded-replica file is read and '
96+
+ 'written through a SQLite engine that has no encryption codec, and the driver never handed the key to '
97+
+ 'a remote database, whose storage encryption the server configures. Delete the key. A local file that '
98+
+ 'must be encrypted at rest belongs on an encrypted disk or volume. '
99+
+ 'Run `os migrate meta --from 17` to list the mechanical edits for the unset and remote-url cases; '
100+
+ '`--write` applies the ones it can prove, and you delete the key from a local or replica datasource by hand.';
101+
84102
// ==========================================================================
85103
// 2a. Transport coherence — what `new TursoDriver` refuses
86104
// ==========================================================================
@@ -151,12 +169,11 @@ interface TursoTransportKeys {
151169
syncUrl?: unknown;
152170
mode?: TursoTransportModeName;
153171
timeoutMs?: unknown;
154-
encryptionKey?: unknown;
155172
}
156173

157174
/** One refusal: the key it sits on and its message. */
158175
interface TursoTransportIssue {
159-
path: 'url' | 'syncUrl' | 'timeoutMs' | 'mode' | 'encryptionKey';
176+
path: 'url' | 'syncUrl' | 'timeoutMs' | 'mode';
160177
message: string;
161178
}
162179

@@ -276,26 +293,6 @@ function tursoTransportIssues(cfg: TursoTransportKeys): TursoTransportIssue[] {
276293
+ "`url: 'file:./data/replica.db'`. For a plain local database, drop `syncUrl` (and `sync`).",
277294
}];
278295
}
279-
if (typeof cfg.encryptionKey === 'string' && cfg.encryptionKey.length > 0) {
280-
// #25020. The url and the mode are coherent (every other shape met a
281-
// refusal above), so what the runtime cannot honour is the KEY: the
282-
// local engine has no encryption codec, so the issue sits on
283-
// `encryptionKey`. An empty key is unset, as the loaders read it.
284-
return [{
285-
path: 'encryptionKey',
286-
message:
287-
`\`encryptionKey\` asks for this datasource's database file to be encrypted, but this datasource is ${arm}, `
288-
+ 'which runs every read and write through a local SQLite engine that has no encryption codec'
289-
+ (mode === 'replica'
290-
? ' (the sync client shares that one file, and a file it encrypted could not be opened by that engine)'
291-
: '')
292-
+ ': nothing would encrypt the file, so it would be written in plaintext that any SQLite reader opens '
293-
+ 'without the key — the turso driver refuses this configuration when it starts. Nothing in this driver '
294-
+ 'encrypts a local or replica file. Drop `encryptionKey`, and encrypt the disk or volume the file lives '
295-
+ "on instead, or keep the data in a remote Turso database, whose storage encryption is the server's to "
296-
+ "configure, not this key's.",
297-
}];
298-
}
299296
return [];
300297
}
301298

@@ -324,32 +321,6 @@ function tursoTransportIssues(cfg: TursoTransportKeys): TursoTransportIssue[] {
324321
+ "file beside `syncUrl`: `url: 'file:./data/replica.db'`.",
325322
});
326323
}
327-
if (
328-
(isFileTursoUrl(url) || url === ':memory:')
329-
&& typeof cfg.encryptionKey === 'string' && cfg.encryptionKey.length > 0
330-
) {
331-
// #25020, patch round 1. Only a FORCED remote mode reaches here with a
332-
// `file:` url or `:memory:` (with no `mode`, those urls select a local
333-
// engine). The key is judged by where the database lives, not by the
334-
// mode: `connect()` hands that url to the libSQL client this driver
335-
// builds, which is handed no key, so the issue sits on `encryptionKey`,
336-
// last, like the local-engine arm's.
337-
issues.push({
338-
path: 'encryptionKey',
339-
message: namesInMemoryTursoDatabase(url)
340-
? "`encryptionKey` asks for this datasource's database to be encrypted, but `url` names an in-memory "
341-
+ "database, and `mode: 'remote'` runs every read and write of it through the libSQL client this "
342-
+ 'driver builds, which is handed no key: there is no file for the key to encrypt, so it would change '
343-
+ 'nothing — the turso driver refuses this configuration when it starts. Drop `encryptionKey`.'
344-
: "`encryptionKey` asks for this datasource's database file to be encrypted, but `url` is a local "
345-
+ "`file:` url, and `mode: 'remote'` runs every read and write of that file through the libSQL client "
346-
+ 'this driver builds, which is handed no key: nothing would encrypt the file, so it would be written '
347-
+ 'in plaintext that any SQLite reader opens without the key — the turso driver refuses this '
348-
+ 'configuration when it starts. Nothing in this driver encrypts a local file. Drop `encryptionKey`, '
349-
+ 'and encrypt the disk or volume the file lives on instead, or point `url` at a remote Turso '
350-
+ "database, whose storage encryption is the server's to configure, not this key's.",
351-
});
352-
}
353324
return issues;
354325
}
355326

@@ -372,21 +343,23 @@ export const TursoConfigSchema = lazySchema(() => z.object({
372343
authToken: z.string().optional().describe('Authentication token for remote database'),
373344

374345
/**
375-
* Encryption Key — encrypts nothing, in any mode, and is REFUSED for a local
376-
* or embedded-replica database, and beside a `file:` url or `:memory:` under
377-
* a forced `mode: 'remote'` (#25020; the refusals are the
378-
* `tursoTransportIssues` arms above, in the spec contract's words — this
379-
* mirror declares no `mode`, so it judges such a config as the local
380-
* database its url selects, and refuses it with that arm's text).
346+
* Tombstone for the REMOVED `encryptionKey` (#25050, ADR-0049
347+
* enforce-or-remove).
348+
*
349+
* It promised to encrypt the local database file at rest with AES-256 and
350+
* was honoured in no mode: the driver reads and writes that file through
351+
* better-sqlite3, which has no encryption codec (so #25020 refused it
352+
* there), and it never handed the key to the libSQL client it builds for a
353+
* remote url, where the key was parsed and dropped. `z.never()` rather than
354+
* a bare deletion for the reason `timeout` below spells out: this shape is a
355+
* plain `z.object`, and a deletion would strip the key in silence.
381356
*
382-
* It used to promise to encrypt the local database file at rest with
383-
* AES-256. The driver reads and writes that file through better-sqlite3,
384-
* which has no encryption codec, so the file was written in plaintext that
385-
* any SQLite reader opened without the key; an embedded replica handed it to
386-
* its sync client only. The driver does not forward it to a remote database
387-
* either, whose storage encryption is the server's.
357+
* The text is `@objectstack/spec`'s `TursoConfigSchema` tombstone on the
358+
* same key, byte for byte, and the constructor's refusal in
359+
* `../turso-driver.ts`; `turso-config-constructor-parity.test.ts` holds the
360+
* three equal.
388361
*/
389-
encryptionKey: z.string().optional().describe("Not honoured in any mode: refused for a local or replica datasource, whose SQLite engine cannot encrypt the file, and for a file: url or :memory: under a forced remote mode, whose libSQL client is handed no key; a remote database's storage encryption is the server's, not set by this key"),
362+
encryptionKey: z.never({ error: () => ENCRYPTION_KEY_RETIRED }).optional().describe(`[REMOVED] ${ENCRYPTION_KEY_RETIRED}`),
390363

391364
/**
392365
* Concurrency Limit.

0 commit comments

Comments
 (0)