Skip to content

fix(driver-turso)!: refuse timeout beside a pre-configured client at construction (ADR-0049 enforce-or-remove) - #16757

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-16617-turso-timeout-client-refusal
Sep 8, 2026
Merged

fix(driver-turso)!: refuse timeout beside a pre-configured client at construction (ADR-0049 enforce-or-remove)#16757
os-zhuang merged 1 commit into
mainfrom
claude/issue-16617-turso-timeout-client-refusal

Conversation

@os-musk

@os-musk os-musk commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator

Fixes #16617

Clause-②: yes

A construction-time refusal narrows the accept set of a published driver option, so this carries the contract-review carrier and stays draft until a tier review returns.

The defect

TursoDriverConfig.timeout installs its window in exactly one place — the fetch spread into createClient(...) inside createRemoteClient(). Two remote sites decide whether that builder runs at all, and both spell the choice identically:

this.libsqlClient = this.tursoConfig.client ?? (await this.createRemoteClient());

A supplied client short-circuits the ?? at both, so the one place the window is installed is never reached. new TursoDriver({ url: 'https://…', client: myClient, timeout: 30000 }) constructed, connected and ran every request unbounded, silently — while timeout's docblock promised "every request the client's HTTP transport makes" and client's docblock said nothing about the key ceasing to apply.

Premises re-derived by symbol on origin/main 7c12e475e0

The card's anchors were measured at 434ca2d64 and the dispatch re-derived at 1ea349f0eb; origin/main advanced to 7c12e475e0 while this was in flight. Re-derived here by symbol, not by line, and the numbers held because turso-driver.ts was untouched in between (last touched by #16650, #16616, #16376):

# premise re-derived reading (packages/drivers/driver-turso/src/turso-driver.ts, 1641 lines)
the window is installed in exactly ONE place :697...(timeoutMs === undefined ? {} : { fetch: fetchBoundedBy(timeoutMs) }), inside createRemoteClient (:690). Sole fetchBoundedBy call site; the other three occurrences are the definition (:284) and two prose references (:341, :687)
BOTH remote client sites bypass it :594 — inside this.remoteTransport.setConnectFactory(...), the lazy connect factory registered in the constructor; and :735connect()'s remote arm. These are the only two tursoConfig.client ?? sites; :745/:746 is the replica arm and uses truthiness, not ??
the replica arm is NOT affected :1712 (post-change; :1617 pre-change) — await boundedBy(this.libsqlClient.sync(), timeoutMs, 'embedded replica sync'), inside sync() at :1705
the contract text promises otherwise the timeout and client docblocks, both edited here

Firing controls. timeout occurs 64 times in the file by occurrence and hits 52 lines — the dispatch's "52" is the line count, and the two are worth separating. A nonsense token (zzzznotatoken) matches 0, so the counter fires.

The refusal, and both bypass sites

The refusal is at construction, before super(), immediately after the existing refuseWebSocketTimeout check. That placement is what covers both sites at once: neither can run, because no driver exists to run them on.

Ordered after the WebSocket refusal deliberately. That refusal's own contract already records that "a caller-supplied client is not consulted — its transport is not the driver's to know", so it already takes every wss:// / ws:// url with a window. Putting this one second means it fires only on compositions the constructor accepts today, and no existing configuration changes which message it gets.

Envelope and text follow #16378 / PR #16616's precedent — VALIDATION_ERROR / 400, no internal issue id in the message (it reaches an operator's boot log and Studio's datasource form), the key named, and both ways out stated. What differs is that this message names two keys and the mode, because the same pair is accepted on the replica arm:

`TursoDriverConfig.timeout` (30000 ms) is set beside `TursoDriverConfig.client` in remote
mode, and on that pair it bounds nothing: the window is the `fetch` this driver hands
@libsql/client while CREATING the remote client, and a pre-configured client is already
built — its transport is not the driver's to replace … Either drop `client` and let the
driver create the remote client, where every request IS bounded and a stalled endpoint
fails as TIMEOUT / 504, or keep `client` and omit `timeout`, building the bound into that
client yourself when you call `createClient({ fetch })`. Replica mode is unaffected: there
`sync()` is bounded whatever client is in use.

Proving both sites are covered, rather than asserting it. A refusal that covered only connect() would leave the lazy factory open — a one-cut fix to a two-site defect — and no assertion about connect() can tell the two apart. So the pins do it in two halves:

  • each site is live, driven independently on the composition that stays accepted (client, no timeout): SITE 1 calls connect() and asserts getLibsqlClient() is the caller's object; SITE 2 never calls connect() at all and drives an operation, so RemoteTransport.ensureConnected() goes through the registered factory, then asserts the same identity plus a non-zero call count on the recording client;
  • the refusal sits upstream of both: on the refused pair, construction throws and the recording client's call log stays empty — a positive observable that nothing downstream ran.

What stays accepted

client with no timeout · timeout with no client · timeout: 0 beside a client · an explicit client: undefined (which the ?? at both sites treats as absent) · the whole replica arm. The replica control is not a construction assertion: it stalls sync() beside a supplied client and a 100 ms window and asserts it still fails TIMEOUT / 504, which makes "the replica arm is untouched" a measurement rather than a claim. The pre-existing turso-driver-timeout.test.ts:149 fixture already pairs client with timeout on that arm and stays green.

Reachability — what is measured, and what is not

⚠️ Not measured, and not claimed to be zero. What was measured:

  • The authoring route cannot reach this at all. buildTursoDriverConfig's reader table emits nine keys — url, authToken, encryptionKey, concurrency, syncUrl, sync, timeout, mode, schemaMode — and client is not among them. Firing control: the same parse reports timeout present, so a false on client is a reading and not a broken parse. The generated protocol reference says the same independently: client is "a live object, not authorable metadata". So no datasource, env var or sys_metadata row can produce the pair.
  • In-repo census: 138 new TursoDriver / createTursoDriver / new TursoDriverCtor construction sites, brace-matched. Outside the new pin file, exactly one pairs the two keys — turso-driver-timeout.test.ts:149, the replica-arm helper, which stays accepted.
  • ⚠️ The first version of that census was wrong and is reported as such. It keyed on client\s*: / timeout\s*: and returned 4 hits; the control fired, but it silently missed object shorthand (client,) and the spread form (...(timeout === undefined ? {} : { timeout })) — including known-true instances in the very files it scanned. Recalibrated to the bare identifier inside the config literal, it returns 8 and now catches both previously-missed classes. The first reading measured nothing and is not the one quoted above.

What could not be measured: any out-of-repo consumer. @objectstack/driver-turso is published, and the closed objectstack-ai/cloud repo is not in this session. A zero-hit census of this repository is not evidence that no deployment composes the pair — which is exactly why the refusal is loud and diagnosable rather than a silent drop or a quiet "the client wins" fallback.

Verification

All at commit fe9ecc72f2 unless noted.

  • pnpm --filter @objectstack/driver-turso test48 files, 1206 tests, 0 failed, 0 skipped, 0 todo, 0 .only (scanned for skip markers in both the log and the sources; no faces were skipped, so there are none to name).
  • pnpm --filter @objectstack/driver-turso typecheck — the package declares one leg, tsc --noEmit (read out of its package.json; there is no sibling tsconfig.test.json). Exit 0 with an empty error body. The new pin file is in the program that grades it: tsc --noEmit --listFiles names it once across 300 files, and a nonsense control filename matches 0.
  • pnpm lint (eslint . --no-inline-config, the repo-wide scan) — exit 0 in 85 s. No narrowing was needed, so none is declared.
  • Gates: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 56 families; all 56 run, every exit code captured after a redirect. Reconciliation verbatim:
Run reconciliation — 56 derived, 56 run, 0 NOT-MEASURED, 0 UNRUN.
✓ dispatch-gates --ran: 56 derived famil(ies) accounted for — 56 run, 0 NOT-MEASURED.

check:type-check-debt and check:dual-build-cjs-loads first returned exit 3 — PREREQUISITE NOT MET, which is not a pass. Rather than declare the gap, the closure was built (turbo run build --filter='./packages/*' --filter='./packages/*/*', 72/72 successful) and both re-run to a real verdict: ✓ check:dual-build-cjs-loads — 104 published require entry point(s) across 67 package(s) load, and check-type-check-coverage --re-measure: OK — 5 ledger entr(ies) re-measured in 77.2s, 55 raw tsc error(s) total, none above its recorded number. driver-turso carries no DEBT ledger entry (0 matches in check-type-check-coverage.mjs; firing control @objectstack/lint matches 3).

Two-leg ablation, blob-hash-verified restore

Direction predicted before the run: the six refusal-side cases go RED, all seven controls stay GREEN.

  • Mutation leg — the three-line guard removed from the constructor. Proven on disk before reading any result: call-site lines 1 → 0, injected marker lines 1, and the blob hash moved dd5b42f3…67c4d32b…. Result: 6 failed | 7 passed (13) — exactly the predicted split, exactly the predicted six.
  • Restore leggit checkout HEAD -- PATH (never the bare form, which would take the mutation back out of the index), verified by blob hash rather than by exit code: RESTORE OK: blob dd5b42f3e76da027cbf8210e3bae25cf04997719 == HEAD blob dd5b42f3e76da027cbf8210e3bae25cf04997719, with git diff HEAD empty. The script carried a trap … EXIT INT TERM with absolute paths seeded from git rev-parse --show-toplevel, and treats an empty hash as failure.

Nothing stayed green that should not have, so there is no coerced layer to report. The subject resolves through relative src imports (./turso-driver.js), not through the package exports, so no dist rebuild sits between the mutation and the observation.

Changeset — minor, derived from the governing text

Governing text used: the WHICH LEVEL maintainer ruling at .github/workflows/pr-automation.yml:667-682 (2026-09-04, decision batch #35, on #15294), mechanized by scripts/check-changeset-no-major.mjs.

Rejected, and why:

  • major — refused during the launch window by check-changeset-no-major.mjs; the ruling puts breaking-ness on "the BREAKING banner plus the ADR-0087 disposition, not … the level".
  • patch — the level the ruling reserves for "a fix( that changes no public surface". This changes which configurations the published constructor accepts, so patch understates the act. AGENTS.md:1028 grades a bug fix patch, but it is the floor against none, not a ceiling, and it is not the governing text on level.
  • skip-changeset — that label is for a diff publishing nothing from any released package; @objectstack/driver-turso publishes.

A **BREAKING** banner and an ADR-0087 disposition are both owed — an accept-set narrowing on a published option, and check-adr-0087-registration.mjs refuses one without the other. The disposition is not-required (no-migration-prescription): no key, spec symbol, Zod schema or stored representation is added, removed or renamed, so objectstack migrate meta has nothing to visit and there is no tombstone to mint. The way out is written as prose, not as a migration table, because that category is refused when the body carries a prescription. This matches the disposition PR #16616 took for the sibling half of the same defect class.

⚠️ A gate observation, filed separately, not fixed here: the level axis in check-changeset-no-major.mjs keys on PUBLISHED_SOURCE_PATH = /^packages\/([^/]+)\/src\//, which does not match packages/drivers/driver-turso/src/** — one path segment too shallow for every nested package directory (packages/drivers/*, packages/adapters/*, packages/apps/*, packages/services/*, …). So on this PR the axis that would have mechanically refused a patch beside Clause-②: yes reads no packages at all. The minor above is therefore chosen from the prose and the precedent, not conferred by a gate that fired.

Files touched

packages/drivers/driver-turso/src/turso-driver.ts · packages/drivers/driver-turso/src/turso-driver-supplied-client-timeout-refusal.test.ts (new) · packages/drivers/driver-turso/README.md · .changeset/driver-turso-supplied-client-timeout-refusal.md.

⚠️ No collision with PR #16720 (#15546), which folds RemoteTransport.aggregate in remote-transport.ts — a different file in the same package. #16378 is not addressed here; PR #16616 is its own change and nothing of it was folded in.

docs/design/driver-turso.md was read and deliberately not edited: its timeoutMs row documents the datasource-authorable key, and the seam that consumes it cannot emit client, so this refusal is unreachable from that surface. content/docs/references/data/driver-turso.mdx is auto-generated and already records client as deliberately non-authorable.

验收备注

Noted while reading, not filed (observations, not defects): turso-driver.ts is 1641 lines and now carries three sibling construction-time refusals whose predicates all re-derive timeoutWindow(config) at the same point; a single guard table would read better, but that is a refactor, not a defect, and it is outside this card.


🤖 Generated with Claude Code

https://claude.ai/code/session_01ADLdAs2pVcH17h9tZKWMBg


Generated by Claude Code

… at construction (ADR-0049 enforce-or-remove)

`TursoDriverConfig.timeout` installs its window in exactly one place — the
`fetch` handed to `@libsql/client` inside `createRemoteClient()`. Both remote
sites that consume a caller-supplied `client` (`connect()` and the lazy connect
factory registered on `RemoteTransport`) spell the choice
`this.tursoConfig.client ?? (await this.createRemoteClient())`, so a supplied
client skipped the builder at both and every request ran unbounded, silently,
while `timeout`'s docblock promised "every request the client's HTTP transport
makes" and `client`'s said nothing about the key ceasing to apply.

The constructor now refuses the pair in remote mode as VALIDATION_ERROR / 400
before super(), naming both keys, the mode and both ways out. Controls pin the
width: `client` with no window, a window with no `client`, `timeout: 0`, an
explicit `client: undefined`, and the replica arm — where `sync()` is bounded
whatever client is in use — all stay accepted. Docblocks and README lines that
promised the window unconditionally now say what is refused.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ADLdAs2pVcH17h9tZKWMBg
@github-actions github-actions Bot added the size/m label Sep 8, 2026
@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/driver-turso, touching 3 documentable anchor(s). ⚠️ 1 changed file(s) yielded no anchor (packages/drivers/driver-turso/README.md), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

2 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/data-modeling/drivers.mdx (via TursoDriver (symbol, a top-level class))
  • content/docs/plugins/packages.mdx (via TursoDriver (symbol, a top-level class))
What this run could not see
  • 1 changed file(s) yielded no anchor (packages/drivers/driver-turso/README.md) — pages documenting those are invisible to this run
  • 1 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 61 of 219 client-bound route-ledger rows — the other 158 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 158: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 6 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 8ccf7a1dfde5ae2280a91f35c468d3b26114e00dpackageMentionDocs.

Which tree this was computed on

This run read content/docs from fbff9664546c153ce8721eabfdcb54451c348a85 — the merge of head fe9ecc72f29049356382d51e6c76b742aaaab10a into base 8ccf7a1dfde5ae2280a91f35c468d3b26114e00d, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin fbff9664546c153ce8721eabfdcb54451c348a85 && git checkout fbff9664546c153ce8721eabfdcb54451c348a85
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 8ccf7a1dfde5ae2280a91f35c468d3b26114e00d fe9ecc72f29049356382d51e6c76b742aaaab10a && git checkout -B drift-repro 8ccf7a1dfde5ae2280a91f35c468d3b26114e00d && git merge --no-ff fe9ecc72f29049356382d51e6c76b742aaaab10a

node scripts/docs-audit/affected-docs.mjs --json 8ccf7a1dfde5ae2280a91f35c468d3b26114e00d

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 8ccf7a1dfde5ae2280a91f35c468d3b26114e00d → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling labels Sep 8, 2026
@os-musk os-musk added needs:contract-review and removed documentation Improvements or additions to documentation tests tooling labels Sep 8, 2026 — with Claude

os-musk commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator Author

Dedup follow-up on the gate observation in the body (the level axis being blind to nested package directories): it is already filed as #16713, opened about three hours before this PR and measured wider than I had it — "51 of 74 workspace packages (all drivers/services/adapters) can pair Clause-②: yes with patch and stay green". So nothing new was opened, and the body's "filed separately" means #16713.

Two nearby cards the same search surfaced, neither the same defect: #16692 (the axis cannot see a shipped bin/ target) and #16361 (the rule is PR-scoped while the fact it judges is package-scoped).

That observation does not change this PR's grading: minor here is chosen from the WHICH LEVEL ruling and the #16616 precedent, not conferred by an axis that fired.


🤖 Generated with Claude Code

https://claude.ai/code/session_01ADLdAs2pVcH17h9tZKWMBg


Generated by Claude Code

Copy link
Copy Markdown
Contributor

Contract review (CONTRACT_REVIEW_TIER, isolated seat) — PR #16757 @ fe9ecc72f2

Verdict: PASS WITH FINDINGS — the refusal is implemented exactly as dispatched, both bypass sites are proven covered, the negative controls are present and CI is green; the findings are procedural (the route was fixed by a seat, not a maintainer) and one optional doc-parity note. Nothing below requires a code change before a maintainer merges.

Ruling implemented: yes — option (a), refuse at construction. ⚠️ No ## Ruling recorded heading exists on #16617 or on this PR. The card body lists three options and states "none recommended here". The direction was fixed by the triage seat (os-zhuang, 分诊席, comment 5572119694) and inherited by the PM seat's claim (5578498363) — both seats, ⛔ not a maintainer. Quoted verbatim from the triage comment, the closest thing to a ruling on record:

方向已定,不需要再裁:选项 (b) 被卡自己排除(窗口骑在 Config.fetch 上,@libsql/client 外部够不着);剩下 (a) 与 (c),而 ADR-0049 的 enforce-or-remove 加上「③ 响亮拒绝优于静默容忍」指向 (a) 在构造时拒绝 timeoutclient 在远程模式下并存。卡自己也写了 (c) 是「documenting 必须论证、不能默认落入」的那条腿。⇒ 交付物是 (a)。

The PR implements exactly that: refuse (not remove — timeout and client both keep their names and types), remote mode only, replica arm untouched, #16378 / #16616 not folded in. Reading independently: (b) is genuinely unreachable (Config.fetch is consumed inside createClient()), and (c) would leave a declared-but-inert composition, which is the ADR-0049 shape itself — so (a) is the right leg. But the choice between (a) and (c) on a published option is a maintainer's to confirm, not a seat's (F1).

Verification (everything read from refs/review/16757 = fe9ecc72f2 and origin/main, never a checkout)

  1. Card and comments. TursoDriverConfig.timeout reaches nothing in remote mode when a pre-configured client is supplied — the HTTP-arm window is applied only by createRemoteClient #16617 has 4 comments: triage (seat), PM claim (seat, Clause-②: yes), dev report, PM round-received. All state the same route. The PM's round-received comment asked the reviewer to attack three points; answered in F4.
  2. Diff vs merge-base 7c12e475e0 — 4 files, +459/−13, matches the body's "Files touched" exactly: .changeset/driver-turso-supplied-client-timeout-refusal.md (A), packages/drivers/driver-turso/README.md (M), packages/drivers/driver-turso/src/turso-driver-supplied-client-timeout-refusal.test.ts (A), packages/drivers/driver-turso/src/turso-driver.ts (M). Governed paths: NOgit diff --name-only 7c12e475e0 refs/review/16757 | grep -E '^(docs/adr/|\.claude/|skills/|AGENTS\.md|CLAUDE\.md|content/docs/releases/)' → none. Governed Surface Queue Guard also green.
  3. The refusal — before/after. Merge-base constructor carries one guard (refuseWebSocketTimeout). Head adds, immediately after it and before super():
    if (mode === 'remote' && timeoutMs !== undefined && config.client !== undefined && config.client !== null) {
      refuseSuppliedClientTimeout(timeoutMs);
    }
    timeoutMs = timeoutWindow(config) returns undefined for 0/unset (:335), so timeout: 0 beside a client is not refused — consistent with the WS refusal's semantics. mode is detectMode(config), so syncUrl compositions resolve to 'replica' and never enter the guard; an explicit mode: 'remote' override does (pinned). The ?? sites the guard protects are :682 (lazy connect factory) and :830 (connect()); :840 is the replica arm and uses truthiness — the body's reading holds. Error code: StandardErrorCode.enum.VALIDATION_ERROR / status: 400. VALIDATION_ERROR is a base member of StandardErrorCode (packages/spec/src/api/errors.zod.ts:54), not a ledger-registered code, so no @objectstack/driver-turso provenance row is owed — check-error-code-provenance.ts sweeps only codes in the registered union, and main already stamps VALIDATION_ERROR from this package at two sites (turso-driver.ts:380, remote-transport.ts:728) with the gate green. Negative controls: timeout without client constructs as remote with timeout retained; client without timeout constructs as remote with the caller's object retained — both pinned. Spec vs constructor: the refusal lives in the constructor and in the exported TursoDriverConfig docblocks (both keys edited); it is not in a Zod schema — and cannot be, because neither authorable schema (packages/spec/src/data/driver/turso.zod.ts, the driver's own src/spec/turso.zod.ts) declares client (documented at turso.zod.ts:46 as "a live object, not authorable metadata"). The exported options type is the only contract face this composition has, and it now says what the constructor does (F2).
  4. Consumers. content/docs/**, docs/**, examples/**, apps/**: the only Turso construction examples are content/docs/plugins/packages.mdx:204 (url + authToken) and docs/design/driver-turso.md:288/300/311 (createTursoDriver, no client). README's own "Custom Client" example passes client without timeout and the PR adds the createClient({ fetch }) remedy beneath it. No surviving example pairs the two keys — no doc break. content/docs/references/data/driver-turso.mdx is generated from the unchanged schema. ADR-0087: no boot/validate notice is expected — the pair is unreachable from the datasource seam (buildTursoDriverConfig never emits client; confirmed at packages/services/service-datasource/src/turso-driver-config.ts reader table), so the refusal at new TursoDriver() IS the notice, and the changeset names both remedies in prose.
  5. Changeset. "@objectstack/driver-turso": minor — correct under WHICH LEVEL (pr-automation.yml:667-682): an accept-set change on a published constructor is not "a fix( that changes no public surface", and major is refused in the launch window. **BREAKING** present; <!-- adr-0087: not-required (no-migration-prescription) … --> parses. Ran both gates against the ref: check-changeset-no-major.mjs --base 7c12e475e0 --head refs/review/16757✓ This diff introduces no major bump; check-adr-0087-registration.mjs✓ 1 declared-breaking changeset(s) … [BREAKING+bang] not-required (no-migration-prescription). FROM/TO: the body states FROM (accepted, window silently undelivered) and TO (refused at construction with the quoted message) and both ways out. Precedent .changeset/driver-turso-ws-timeout-refusal.md (fix(driver-turso)!: refuse timeout beside a wss:// / ws:// url at construction (ADR-0049 enforce-or-remove) #16616) carries the identical level + disposition shape. ⚠️ The level axis in check-changeset-no-major.mjs did not grade this diff ([finding] The changeset LEVEL axis is blind to every NESTED package: packages/*/src/** matches one segment, so 51 of 74 workspace packages (all drivers/services/adapters) can pair Clause-②: yes with patch and stay green #16713, nested package path) — the minor rests on the prose ruling and I concur with it.
  6. Tests. New pin file: 6 refusal-side cases (3 × it.each schemes, forced mode: 'remote', createTursoDriver, "reaches NEITHER site") and 7 controls (SITE 1 via connect(), SITE 2 via the lazy factory with no connect(), client alone, timeout alone, timeout: 0 + client, client: undefined + timeout, replica arm with stalled sync() still failing TIMEOUT/504). Reverting the three-line guard makes every refusal case red (the constructor returns a driver; refusalOf yields null, expect(refusal).not.toBeNull() fails) — the body's 6/7 ablation split is the predicted and correct one. Assertions pin code, status and message substrings (both key names, ${WINDOW_MS} ms, remote, both ways out) — never a bare toThrow(). .skip/.only/.todo: 0. Typecheck: the file sits under src/ of the package's single tsc --noEmit leg; "Type Check · workspace/source gates/consumer gates/debt ledger" all green on head. The pre-existing turso-driver-timeout.test.ts:149 replica fixture pairing client + timeout stays accepted — an independent control on the remote-only scoping. ⚠️ I did not re-run the suite locally (no checkout on this seat); the 1206-test count is the PR's, the green is CI's.
  7. CI on head fe9ecc72f2. 46 check runs: 34 success, 12 skipped, 0 failure (skips are the expected Auto Label / Check PR Size / Packed-tarball / Console Pin Gate / Build Docs no-ops). mergeable_state: clean. Branch is 22 commits behind origin/main (87fad14b8); git merge-tree against current main is conflict-free; main's in-flight touch to this package is remote-transport.ts (PR fix(driver-sql): an all-NULL sum answers 0 on every face — fold at the aggregate door, conformance cell on every enrolled face (#15546) #16720), a different file. 0 reviews on the PR.

Findings

Maintainer-only merge: yes — breaking ! with Clause-②: yes, draft, needs:contract-review hung, and the route rests on a seat's reading (F1). This seat did not approve, request changes, ready, label, or touch the branch.


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

3 participants