You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 79fbad6
Browse filesBrowse the repository at this point in the historyBrowse files
fix(spec,driver-turso)!: refuse a forced mode 'replica' with no syncUrl at authoring and at construction
A turso config that forces mode 'replica' on a file: url with no syncUrl
(or an empty one) was accepted by both TursoConfigSchema copies and by
the constructor, and ran as a plain local database that never synced.
Both doors now refuse it with one message: the spec contract on mode,
and new TursoDriver with VALIDATION_ERROR / 400 after the sync refusal,
its text a module constant pinned byte-equal by the parity table.
The parity row, the unrecognised-url WIDENED cell and the rider control
flip to refused; a new constructor test file and spec tests pin the
refusal, its order and its width. ADR-0087: a new D3 entry
turso-config-forced-replica-without-sync-url-refused, registry
regenerated.
Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N8TPEsoJxPsdSdNKGnNGEN
fix(spec,driver-turso)!: a turso config that forces `mode: 'replica'` with no `syncUrl` is refused where it is written and when the driver is built, instead of running as a plain local database that never syncs
7
+
8
+
Clause-②: yes (narrowing) — the accept set of the `turso``datasource.config` contract narrows by one combination. No key is added, removed or renamed, and no exported symbol moves.
9
+
10
+
An embedded replica is a local file kept in sync with the remote named in `syncUrl`. A config that forced `mode: 'replica'` on a `file:` url with no `syncUrl` (or an empty one) was accepted by `@objectstack/spec`'s `TursoConfigSchema`, by the published mirror in `@objectstack/driver-turso`, and by `new TursoDriver()`. Measured on the built driver before this change, with and without `sync`: it constructed with `transportMode``'replica'`, `isSyncEnabled()` answered `false`, no sync interval started, the sync call did nothing, and every read and write went to the local file. A datasource declared as a replica ran as a plain local database that never replicated, with no error and no warning.
11
+
12
+
**BREAKING** accept-set narrowing on a published schema and a published constructor, shipped as `minor` under the repo's launch-window convention for breaking changes (`scripts/check-changeset-no-major.mjs`). Refused now, at both doors together, with one message whose prescription names both ways out:
13
+
14
+
-**at authoring**, as one `custom` issue on `mode` (`config.mode` on a datasource): `DatasourceSchema`, `validateDriverConfig`, `defineStack` / `os validate`, and a save or test connection through the datasource admin service;
15
+
-**at construction**, `VALIDATION_ERROR` / 400 from `new TursoDriver()` (and `createTursoDriver()`), before any client or database is opened.
16
+
17
+
The message is the same text at both doors, and a test holds the constructor's copy equal to the schema's issue byte for byte. The sibling refusals keep their order. A forced replica on a remote url, an in-memory url or a bare path still meets its `url` refusal first. One with `sync` and no `syncUrl` still meets the `sync` refusal first; the schema now reports the `mode` issue beside it. The driver mirror declares no `mode` key and strips an authored one, so it cannot see a forced mode: this refusal reaches it only as byte-identical text, and the spec contract and the constructor are the two doors that judge it.
18
+
19
+
### Migration: FROM → TO
20
+
21
+
| You wrote | Write instead |
22
+
| --- | --- |
23
+
|`url: 'file:./data/replica.db', mode: 'replica'` (no `syncUrl`, or `syncUrl: ''`) | an embedded replica: keep the `file:` url and name the remote, `syncUrl: 'libsql://my-db.turso.io'`|
24
+
| the same | a plain local database: drop `mode` (`url: 'file:./data/app.db'` alone) |
25
+
26
+
A datasource row stored in this shape is not re-parsed when it loads, so it now fails when the driver is built. `factory.create` throws the refusal. The connection service records the datasource as `failed-degraded` with the message, and a test connection answers `ok: false` ("Failed to build driver: …"). Under ADR-0062 D5, the boot fails fast when objects bind to that datasource or are routed to it, or when it is boot-critical, unless `OS_ALLOW_DRIVER_CONNECT_FAILURE` is set. Otherwise it is left unconnected with a warning. Before this change the same row booted and ran as a local database. The way out is the table above.
27
+
28
+
Blast radius, measured on this tree: no example, template, published skill or hand-written doc authors the shape, and no host default or environment variable sets `mode` (a turso `mode` reaches the driver only from an authored `datasource.config`). Whether any out-of-repo deployment declares such a config is NOT measured and is not claimed to be zero.
{name: "file: under a forced mode: 'replica'",config: {url: FILE,mode: 'replica'},ctor: 'accept'},
105
+
{name: "file: + syncUrl under a forced mode: 'replica'",config: {url: FILE,mode: 'replica',syncUrl: REMOTE,sync: {onConnect: false}},ctor: 'accept'},
101
106
{name: "file: + syncUrl under a forced mode: 'local'",config: {url: FILE,mode: 'local',syncUrl: REMOTE,sync: {onConnect: false}},ctor: 'accept'},
102
107
{name: "libsql:// under a forced mode: 'remote'",config: {url: REMOTE,mode: 'remote'},ctor: 'accept'},
103
108
{name: "file: under a forced mode: 'remote'",config: {url: FILE,mode: 'remote'},ctor: 'accept'},
@@ -147,13 +152,24 @@ const ROWS: Row[] = [
147
152
{name: 'sync with no syncUrl',config: {url: FILE,sync: {intervalSeconds: 60}},ctor: 'refuse',refusedOn: 'sync'},
148
153
{name: 'sync with no syncUrl on a remote url',config: {url: REMOTE,sync: {intervalSeconds: 60}},ctor: 'refuse',refusedOn: 'sync'},
149
154
{name: "sync with no syncUrl under a forced mode: 'remote'",config: {url: REMOTE,mode: 'remote',sync: {onConnect: true}},ctor: 'refuse',refusedOn: 'sync'},
150
-
{name: "sync with no syncUrl under a forced mode: 'replica'",config: {url: FILE,mode: 'replica',sync: {intervalSeconds: 60}},ctor: 'refuse',refusedOn: 'sync'},
155
+
// The spec contract raises BOTH issues here (`sync`, then `mode`); the
156
+
// constructor throws one, the `sync` refusal, which is the spec's first.
157
+
{name: "sync with no syncUrl under a forced mode: 'replica'",config: {url: FILE,mode: 'replica',sync: {intervalSeconds: 60}},ctor: 'refuse',refusedOn: 'sync',issues: 2},
0 commit comments