Repository navigation
Commit bea6d2e
Fixes #20200
Clause-②: no (narrowing)
The `Clause-②` line above is the amended claim's (comment 5869128452,
unchanged in 5871001040), copied as it stands. The changeset carries the
same value.
Session `session_01N8TPEsoJxPsdSdNKGnNGEN` (PM dispatch, `domain:engine`
seat 1, mode:subagent), branch
`claude/issue-20200-turso-remote-sync-refused`. The branch starts at
`e01d34730`, which already contains PR #20422 and PR #20425. It was then
merged with `origin/main` at `87c37aec1` in a true merge commit. **Every
reading below was taken at head `2242ad513`** unless it says otherwise.
## What changes
`new TursoDriver(...)` refuses two configurations it used to build while
ignoring one of their keys. Both are refused with `VALIDATION_ERROR` /
400, before `super()`, after the constructor's three existing refusals,
so no configuration they already refuse gets a different message:
- **`syncUrl` under a forced `mode: 'remote'`.** A remote url beside
`syncUrl` with no `mode` was already refused, as a replica on a remote
url (`localEngineDefect`), so only a forced remote mode reaches this
refusal.
- **`sync` with no `syncUrl`**, in every mode: local, replica and
remote. An empty `syncUrl` counts as unset, which matches `detectMode`,
`connect()`, `sync()` and the spec's refinement.
Each refusal's message is `@objectstack/spec`'s `TursoConfigSchema`
issue message for that key, byte for byte. Per the seat's ruling (option
A of the report `5869079328`), each is a module-level constant in
`turso-driver.ts`: `REMOTE_MODE_SYNC_URL_REFUSAL` and
`SYNC_WITHOUT_SYNC_URL_REFUSAL`. The parity test holds each constant
equal to the schema's issue, read from the built spec dist. No new
`packages/spec` export.
**The message must be true where it is thrown** (seat ruling 2, a
declared cross-lane text edit). The spec's `syncUrl`-under-remote text
said "the turso driver never hands `syncUrl` to the remote client and
runs no sync, so the setting changes nothing". That clause now reads
"the turso driver refuses this configuration when it starts", the form
its sibling refusals in `tursoTransportIssues` use. The rest of the
message is byte-identical. The three copies now read the same: the spec,
the driver's `TursoConfigSchema` mirror and the constructor constant.
The `sync` message is reused unchanged. The ADR-0087 entry
`18.turso-config-transport-mismatch-refused` has its `reason` sentence
on stored rows corrected. It now says the constructor also refuses
`syncUrl` under a forced remote mode and `sync` with no `syncUrl` when
the datasource boots, citing #20200. Patch round 1 also put its header
comment and its "One more it builds and then ignores" clause in the past
tense, bounded by #20200. Nothing else in the entry moved.
`check:generated` then proved `src/migrations/registry.ts` stale,
because the registry embeds the entry text, and `check:generated --fix`
regenerated it: a 4-line text diff. No gate refused editing a registered
entry.
## H1: before and after (dist probe, 9 configs)
The same scratch script ran against
`packages/drivers/driver-turso/dist/index.mjs`. Before is at `dbddf02c1`
(origin/main when the first round measured). After is this branch's
build.
| config | before (`dbddf02c1`) | after |
| --- | --- | --- |
| `libsql://` + `mode: 'remote'` + `syncUrl` + `sync` | constructs,
connects, `isSyncEnabled()` true, no interval, `sync()` rejects
`SYNC_NOT_SUPPORTED` | refused, VALIDATION_ERROR / 400, `syncUrl`
message |
| `libsql://` + `mode: 'remote'` + `syncUrl` | the same | refused,
`syncUrl` message |
| `file:` + `mode: 'remote'` + `syncUrl` + `sync` | constructs,
connects, `isSyncEnabled()` true, no interval, `sync()` rejects
`SyncNotSupported("File")` | refused, `syncUrl` message |
| `libsql://` + `mode: 'remote'` + `sync`, no `syncUrl` | constructs,
`isSyncEnabled()` false, `sync()` a no-op | refused, `sync` message |
| `libsql://` (no mode) + `sync`, no `syncUrl` | the same | refused,
`sync` message |
| CONTROL `libsql://` + `mode: 'remote'` | constructs, connects,
`isSyncEnabled()` false | unchanged |
| RIDER `file:` + `mode: 'replica'`, no `syncUrl` | constructs as
`replica`, `isSyncEnabled()` false, `sync()` a no-op | **unchanged (see
the rider section below)** |
| RIDER + `sync`, no `syncUrl` | the same | refused, `sync` message (the
`sync` key, not the rider) |
| `file:` + `sync`, no `syncUrl`, no mode | local, `sync` ignored |
refused, `sync` message |
## H4: what a stored row answers at boot now
`buildTursoDriverConfig`
(`packages/services/service-datasource/src/turso-driver-config.ts`, read
and not edited) forwards a stored row's `syncUrl`, `sync` and `mode`
unparsed. PR #20199's ADR-0087 entry is semantic, so no D2 conversion
rewrites such a row either. A row stored before PR #20199 with a remote
url, `mode: 'remote'` and `syncUrl`, or with `sync` and no `syncUrl`,
therefore reaches the constructor as written:
- `factory.create` throws the refusal
(`default-datasource-driver-factory.ts` for open-core,
`turso-driver-factory.ts` for the host default).
- `DatasourceConnectionService` catches it and records the datasource as
`failed-degraded`, with "datasource NAME: connect failed — MESSAGE".
- Under ADR-0062 D5, 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.
- A test connection answers `ok: false`, with "Failed to build driver:
MESSAGE".
Before this change the same row booted, reported sync as enabled, and
never synced. No in-repo caller reads `isSyncEnabled()` or calls the
driver's sync outside `@objectstack/driver-turso`'s own tests. The
changeset's FROM → TO paragraph carries these readings and the way out:
drop `syncUrl` / `sync` from a remote config, or use a `file:` url with
the remote in `syncUrl`.
## The rider stays: `mode: 'replica'` on a `file:` url with no `syncUrl`
This configuration still constructs and runs as a plain local database.
The table's H1 rider row shows it: a declared replica that never syncs.
It is deliberately not refused here. A constructor-only refusal would
make construction refuse a configuration both `TursoConfigSchema` copies
accept, which reopens #19977's defect class in reverse (a datasource
that authors clean and fails at boot). The parity table would go red on
its row "file: under a forced mode: 'replica'", and
`turso-driver-unrecognised-url-refusal.test.ts` pins `FILE: + mode
'replica', no syncUrl` as accepted. Closing it needs the spec half and
the constructor half together, and the spec's accept set is outside this
card, so the seat files it as its own card. The new test file pins the
rider as still accepted, so whoever closes it moves that pin on purpose.
## Tests
| suite at `2242ad513` | result |
| --- | --- |
| `@objectstack/driver-turso` vitest, whole package | 75 files · 2014
passed · 18 skipped · exit 0 |
| `@objectstack/driver-turso` typecheck (`tsc --noEmit`) | exit 0;
`--listFilesOnly` on the pre-merge commit shows both touched test files
in the program |
| `@objectstack/spec` vitest `--project local`, 3 shards | 564 files ·
16640 passed · 1 todo (6026 + 5141 + 5473), exit 0 on each shard |
| `@objectstack/spec` typecheck (tsc + scripts + `check:test-typecheck`)
| exit 0 |
The 18 skips are the parity table's forced-mode rows for the mirror,
which strips `mode`: 16 before, plus the two new forced-mode `sync`
rows.
- **`spec/turso-config-constructor-parity.test.ts`:** the four `inert`
rows flip to `ctor: 'refuse'` (three `syncUrl`, one `sync`). Four `sync`
rows are added: a remote url, a forced remote, a forced replica on
`file:`, and an empty `syncUrl`. The `inert` floor moves from at least 4
to exactly 0, with floors added for `syncUrl` (at least 3), `sync` (at
least 5) and the sync-key refusals (at least 8). A new table,
`SYNC_KEY_REFUSALS`, asserts for each of those 8 rows that the
constructor's `error.message` equals the spec issue's message.
- **`turso-driver-ignored-sync-key-refusal.test.ts` (new):** both
refusals are asserted as the envelope (`code` + `status`) plus the
message's first sentence, across `libsql://`, `https://`, `file:`,
`:memory:`, forced remote, forced replica and an empty `syncUrl`.
`createTursoDriver()` is covered too. Controls: forced remote with no
`syncUrl` connects and reports sync off; a `file:` replica beside
`syncUrl`; `sync` beside `syncUrl` under forced `mode: 'local'`; the
rider.
- **`packages/spec/src/data/driver/turso.test.ts`:** one assertion and
its test title pinned the old clause ("never hands `syncUrl` to the
remote client and runs no sync"). They now pin the new clause. This file
is not named in the claim's surface; the change is the mechanical
consequence of ruling 2's text edit (see Deviations).
**Reverse verification**, via `scripts/ablation-replace.mjs` from the
committed state (turso-driver.ts blob `afe3ad31`). The driver tests
import `../turso-driver` from source, so no build is involved.
Directions were predicted before each run, and all three matched:
1. `if (mode === 'remote' && config.syncUrl) {` became `if (false && …)
{`, mutation landed (anchor 1 → 0, blob `afe3ad31` → `5f9aca88`). **11
failed** / 169 passed: exactly the 5 `syncUrl` cases in the new file,
plus the 3 `syncUrl` rows' constructor verdicts and their 3 message
pins. Restored: blob == HEAD, `git diff HEAD` empty.
2. `if (config.sync && !config.syncUrl) {` got the same mutation (blob →
`32b6a28c`). **16 failed** / 164 passed: the 6 `sync` cases, plus 5
constructor verdicts and 5 message pins. Restored the same way.
3. One byte of the copy: a doubled space inside the `syncUrl` constant,
after its first sentence (blob → `084c8d96`). **3 failed**: exactly the
3 `syncUrl` message pins, while every verdict and first-sentence case
stayed green. This proves the byte-equality pin is what holds the copy.
Restored the same way.
## Gates
`node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack`, run after the last commit at `2242ad513`,
lists 9 paths vs merge base `87c37aec1` and **91 commands**. Every
command ran, with its exit code written to disk before any pipe. `--ran`
reconciliation reads `91 derived famil(ies) accounted for — 89 run, 2
NOT-MEASURED (2 DERIVED from a recorded exit 3)`, with 0 UNRUN.
- **NOT MEASURED (exit 3, PREREQUISITE NOT MET):**
`check:dual-build-cjs-loads` (dozens of workspace packages have no
`dist/`) and `check:type-check-debt` (it needs a whole-workspace build).
Both need a full-workspace build, which is CI's run.
- **Run twice:** `check:doc-formula-expressions` and
`check:lean-entry-closure` first answered exit 3. They are exit 0 after
building `@objectstack/lint` and `@objectstack/objectql` with their
closures.
- **Notable readings:**
- `check-adr-0087-registration` → `not-required (already-registered)`
for `turso-config-transport-mismatch-refused`, clause-② narrowing,
BREAKING, bang;
- `check-changeset-no-major` exit 0;
- `check:migration-registry` → `registry.ts is current`;
- `check:driver-conformance`, `check:nul-bytes`, `check:doc-authoring`,
`check:test-source-alias`, `check:cross-package-test-inputs`,
`check:api-surface`, `check:authorable-surface`, `check:docs`,
`check:spec-changes` and `check:upgrade-guide` → exit 0.
- **Roster families under a touched directory, also run:** five
artifact-roster families whose roster sits under a directory this diff
touches, all exit 0: `check-changeset-fixed`, spec
`check:meta-url-spelling`, `check:authz-resolver`,
`check:error-code-casing` and `check:filter-alias-parity`.
- **Narrowed lint:** `eslint --no-inline-config --format json` over the
8 changed TS files reports 8 files, 0 errors and 0 warnings. The
population is `eslint.config.mjs`'s lint object, `files:
['**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}']`; the changeset `.md` is
outside it. Invariance holds because that config never enables
type-aware linting (no `parserOptions.project`, per its own header), so
this diff cannot move any untouched file's verdict. The full `pnpm lint`
is CI's.
- **Not run locally, left to CI:**
- the whole-workspace type-check lanes;
- the Test Core, Dogfood and Build Core jobs;
- downstream suites of `@objectstack/service-datasource`,
`@objectstack/runtime` and `@objectstack/cli`. Their turso fixtures go
through `buildTursoDriverConfig` or a capturing constructor and never
build the real driver. The downstream real-driver constructions are both
in dogfood's `date-bucket-parity-turso.test.ts`: `new TursoDriver({ url:
':memory:' })`, and at `:144` a remote `{ url:
'libsql://test-db.turso.io', authToken }`. Neither carries a sync key,
so neither refusal touches them.
**Driver-conformance ledger (`lanes/engine.md`):**
`check:driver-conformance` read `OK — 50 covered cell(s), 0 in the DEBT
ledger, 0 exempt` both before (`dbddf02c1`) and after (`2242ad513`).
`driver-turso` is `ok` on all 10 case-sets both times. No movement.
## Deviations (declared)
- `packages/spec/src/data/driver/turso.test.ts`: one assertion and one
test title, rewritten because ruling 2's text edit makes the old pin
false. It is outside the claim's listed surface ("nothing else in
`packages/spec`"). Without it the spec suite goes red.
- `turso-driver.ts` outside the constructor body. It carries the two
module constants and a small throw helper (`refuseIgnoredSyncKey`)
beside `refuseSuppliedClientTimeout`, as ruling 1 prescribes. It also
extends the TSDoc on `TursoDriverConfig.syncUrl` and `.sync` so the
declared config names the two new refusals. No other region of the file
changed.
## Acceptance notes
- **The mirror's dormant copy is now kept equal by this diff.** The
driver mirror (`src/spec/turso.zod.ts`) strips `mode`, so its copy of
the `syncUrl`-under-remote text is still unreachable through a parse,
and no test can hold it equal. This diff applies the same one-clause
edit there, so the three copies read identically today.
- **The spec wording item from the report is done here** (ruling 2).
- **Comments that described the ignored sync keys: corrected in patch
round 1** (the amended claim 5871001040). Every comment in this diff's
files that described `syncUrl` under a forced remote mode, or `sync`
with no `syncUrl`, as constructed and ignored now says what holds since
#20200 (list below). A `git grep -n -i ignore` over the touched files
finds no such wording left. What remains is only
`packages/drivers/driver-turso/README.md`'s list of constructor
refusals. It is scoped to the local-engine refusals and does not name
the two new ones: incomplete, not false (the review's reading), and a
docs follow-up. It is not runtime text.
- Loader fixtures in `@objectstack/service-datasource`
(`turso-driver-config.test.ts`, the `mode: 'remote'` + `syncUrl`
key-list case) spell a configuration the real constructor now refuses.
They exercise `buildTursoDriverConfig` only and never construct the
driver, and the package is fenced, so they are unchanged.
## Patch round 1 (text only, head `7052b9111`)
The at-tier review 5870987840 PASSed `2242ad513` and escalated comments
this diff made false. With the `inert` floor at 0, the driver no longer
constructs and ignores anything the schemas refuse. Per the amended
claim 5871001040, those comments are corrected here as text only. No
logic and no test assertion changed.
- **Spec `packages/spec/src/data/driver/turso.zod.ts`:**
- section heading 1b "refuses or ignores" → "refuses";
- the intro "or constructs and then ignores" → "or, until #20200,
constructed and then ignored";
- the `syncUrl`-under-remote bullet ("which the driver accepts and then
IGNORES … That arm has no constructor refusal behind it") is now in the
past tense and says the constructor refuses it too since #20200;
- "Nothing the constructor accepts is refused here, the
`syncUrl`-under-`mode: 'remote'` arm aside" now says that exception has
been a constructor refusal too since #20200;
- the superRefine comment "What the driver refuses at construction, or
constructs and ignores" → "What the driver refuses at construction".
- **Mirror `packages/drivers/driver-turso/src/spec/turso.zod.ts`:**
section heading 2a "refuses or ignores" → "refuses"; the intro "or
constructs and ignores" → "or, until #20200, constructed and ignored";
the superRefine comment corrected as in the spec.
- **Spec `turso.test.ts`:** the block comment "(which it constructs and
ignores)" → "(since #20200 that includes `syncUrl` under a forced `mode:
'remote'`, which it used to construct and ignore)".
- **Parity test:** two comments. The header's "(or constructs and
ignores)" → "(or, until #20200, constructed and ignored)", and "nothing
it accepts but the declared-and-ignored keys" → "… but an `inert` row's
declared-and-ignored key (none since #20200)".
- **D3 entry `18.turso-config-transport-mismatch-refused`:**
- its header comment now reads "the one combination the driver used to
build and then ignore (syncUrl under a forced remote mode), which the
constructor refuses too since #20200";
- its `reason` clause "One more it builds and then ignores … Nothing the
constructor accepts is refused, that key aside" is in the past tense and
bounded by #20200 ("One more it built and then ignored until #20200 …";
"Nothing the constructor accepts is refused (at #19977 that key was the
one exception; since #20200 there is none)");
- nothing else in the entry moved, and `registry.ts` was regenerated by
`gen:migration-registry`, its hunk equal to the entry's.
- **Left as they are, each true:** "would ignore" (a conditional, naming
what the refusals prevent), the `inert` mechanism's own definition (with
none today), the "before" measurement table in the new test file, and
`@libsql/client` ignoring `syncUrl` beside a remote url (a fact about
the client).
Readings at `7052b9111`. That head is the round's two commits plus a
true merge of `origin/main` `8cdbe0c6e`, which regenerated `registry.ts`
for its own new entry; the registry is current, with 308 semantic
entries.
- **CI on `2242ad513` before the push:** 35 check runs, 32 success, 3
skipped, 0 failed. Test Core 1/6, 3/6 and 5/6 and Type Check · workspace
all concluded success.
- **Tests:** `@objectstack/driver-turso` 75 files · 2014 passed · 18
skipped, typecheck exit 0. `@objectstack/spec` `--project local` in 3
shards: 6025 + 5141 (1 todo) + 5473 passed, exit 0 on each. A first
shard-2 attempt was killed by the runner's own timeout and re-run. Spec
typecheck exit 0.
- **Gates:** `dispatch-gates --commands` finds 9 paths vs merge base
`8cdbe0c6e` and 91 commands, all run with exit codes recorded before any
pipe. `--ran` reads `91 derived famil(ies) accounted for — 89 run, 2
NOT-MEASURED (2 DERIVED from a recorded exit 3)`, 0 UNRUN; the two NOT
MEASURED are `check:dual-build-cjs-loads` and `check:type-check-debt`
(whole-workspace build; both green in CI at `2242ad513`). Also run: the
five roster families under touched directories, and spec
`check:generated` ("All 15 generated artifacts are up to date"), all
exit 0.
- **Other readings:** `check:driver-conformance` is unchanged at 50
covered, 0 DEBT. The narrowed eslint run finds 8 files, 0 errors. The
control-byte scan finds nothing.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01N8TPEsoJxPsdSdNKGnNGEN)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
1 parent fbeb56e commit bea6d2e
9 files changed
Lines changed: 386 additions & 76 deletions
File tree
- .changeset
- packages
- drivers/driver-turso/src
- spec
- spec/src
- data/driver
- migrations
- entries/semantic
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
10 | 10 | | |
11 | 11 | | |
12 | 12 | | |
13 | | - | |
14 | | - | |
| 13 | + | |
| 14 | + | |
15 | 15 | | |
16 | 16 | | |
17 | 17 | | |
18 | 18 | | |
19 | | - | |
20 | | - | |
21 | | - | |
22 | | - | |
23 | | - | |
24 | | - | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
25 | 31 | | |
26 | 32 | | |
27 | 33 | | |
| |||
69 | 75 | | |
70 | 76 | | |
71 | 77 | | |
72 | | - | |
| 78 | + | |
73 | 79 | | |
74 | 80 | | |
75 | 81 | | |
| |||
134 | 140 | | |
135 | 141 | | |
136 | 142 | | |
137 | | - | |
138 | | - | |
139 | | - | |
140 | | - | |
141 | | - | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
142 | 152 | | |
143 | 153 | | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
144 | 159 | | |
145 | 160 | | |
146 | 161 | | |
| |||
159 | 174 | | |
160 | 175 | | |
161 | 176 | | |
162 | | - | |
| 177 | + | |
163 | 178 | | |
164 | 179 | | |
165 | | - | |
| 180 | + | |
166 | 181 | | |
167 | 182 | | |
168 | 183 | | |
169 | 184 | | |
170 | | - | |
| 185 | + | |
171 | 186 | | |
172 | 187 | | |
173 | 188 | | |
| |||
192 | 207 | | |
193 | 208 | | |
194 | 209 | | |
195 | | - | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
196 | 216 | | |
197 | 217 | | |
198 | 218 | | |
199 | 219 | | |
200 | 220 | | |
201 | | - | |
| 221 | + | |
202 | 222 | | |
203 | 223 | | |
| 224 | + | |
204 | 225 | | |
205 | 226 | | |
206 | | - | |
| 227 | + | |
207 | 228 | | |
208 | 229 | | |
209 | 230 | | |
| |||
219 | 240 | | |
220 | 241 | | |
221 | 242 | | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
222 | 255 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
82 | 82 | | |
83 | 83 | | |
84 | 84 | | |
85 | | - | |
| 85 | + | |
86 | 86 | | |
87 | 87 | | |
88 | 88 | | |
89 | 89 | | |
90 | | - | |
91 | | - | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
92 | 93 | | |
93 | 94 | | |
94 | 95 | | |
| |||
260 | 261 | | |
261 | 262 | | |
262 | 263 | | |
263 | | - | |
264 | | - | |
| 264 | + | |
| 265 | + | |
265 | 266 | | |
266 | 267 | | |
267 | 268 | | |
| |||
390 | 391 | | |
391 | 392 | | |
392 | 393 | | |
393 | | - | |
394 | | - | |
| 394 | + | |
| 395 | + | |
395 | 396 | | |
396 | 397 | | |
397 | 398 | | |
| |||
0 commit comments