feat(driver-turso)!: timeout bounds remote operations; localPath and wasm leave the published config schema (ADR-0049 enforce-or-remove) - #16376
Conversation
…sm tombstoned (#16024) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ARYe3yQTQCUFm5qPYNgKaJ
📓 Docs Drift CheckThis PR changes 2 package(s): 2 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
What this run could not see
Coarse fallback — 133 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 0834c59ee3d28f5c1f0df75076c69617d5e99891 && git checkout 0834c59ee3d28f5c1f0df75076c69617d5e99891
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin f2f6684cd5e93032ed5d82a1848784b6026a8a03 58d167bed5b52b42d77e8fec8937337a3a6cfa63 && git checkout -B drift-repro f2f6684cd5e93032ed5d82a1848784b6026a8a03 && git merge --no-ff 58d167bed5b52b42d77e8fec8937337a3a6cfa63
node scripts/docs-audit/affected-docs.mjs --json f2f6684cd5e93032ed5d82a1848784b6026a8a03
|
…r, not patch (#16024) The Check Changeset step's WHICH LEVEL rule (maintainer, 2026-09-04, batch #35): a purely additive widening of a published package's public surface takes at least minor, and the act sets the floor. The new D3 entry in packages/spec/src/migrations/registry.ts is that act for @objectstack/spec. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ARYe3yQTQCUFm5qPYNgKaJ
|
| step | result |
|---|---|
| Set up job → Install dependencies → Compute this shard's package set | success (all of them) |
| Run this shard's tests | cancelled |
| Publish turbo run summary · Test completeness guard · Post Checkout | success |
| Attest this shard ran and passed | skipped |
The log ends ##[error]The operation was canceled. mid-stream through @objectstack/cli's suite, with no Failed Tests block, no Test Files … failed, and no ELIFECYCLE. ⇒ nothing asserted false. The roll-up Test Core reports failure because shard 5 produced no attestation, not because it produced a failing one.
Why it is a timeout, and why it is structural rather than a flake
| PR | Test Core (5/6) |
duration |
|---|---|---|
| this PR | cancelled | 30.3 min |
| #16057 | success | 25.7 min |
| #16363 | success | 25.2 min |
| #16326 | success | 20.1 min |
30.3 minutes against a 30-minute job ceiling, with every sibling landing in 20–26. ⭐ And the siblings are not comparable work: they touch packages/core / packages/objectql only, while this PR touches packages/spec, which invalidates the Turbo cache for everything downstream — so this shard rebuilds far more before it runs a single test. The extra minutes are bought by the rebuild, not by slower tests.
⛔ "Flake" is not the right word and I am not using it. A flake is a real run with a nondeterministic result; this is a run that never finished. The margin (0.3 min over) makes a warm-cache re-run plausible, which is precisely why a green re-run would prove nothing about the underlying fit.
What I am doing, and what I am not
- Re-running the shard once — the permitted single re-run for a job that died before its test body completed.
- ⛔ Not skipping, quarantining or narrowing any test to fit the window. ⛔ Not pushing an empty commit. ⛔ Not widening this PR to "fix CI".
- If the re-run also times out, this is not something this PR can resolve from inside itself: a spec-touching change does not fit shard 5's current ceiling, and that is a CI-capacity question for whoever owns the sharding — I will say so and leave the PR watched rather than hand it to anyone.
Everything else on this PR is green: 37 checks, 0 failing apart from the shard and its roll-up. needs:contract-review remains hung on both carriers and the PR stays a draft; ⛔ nothing here is a release.
Generated by Claude Code
The re-run timed out at exactly the same 30.3 minutes. This PR cannot fix it from inside itself.PM (
⭐ Identical to the tenth of a minute. That is not variance around a boundary — it is a hard ceiling being hit twice by work that does not fit under it. The comparison stands: three sibling PRs pass this shard in 20–26 min, and they touch Current state at this head: 37/37 completed, 2 failing — the shard and its roll-up, the roll-up only because a cancelled shard publishes no attestation. Nothing in this PR asserts false; every other check is green. ⛔ What I am not doing, and why
What this actually isA CI-capacity question owned by whoever owns the sharding, not a defect in this diff. Filed as a finding so it is tracked rather than rediscovered by the next
This PR stays a draft with Generated by Claude Code |
…rso-inert-config-keys
…mirror does not declare `mode` (#16024) Two prose repairs the contract review raised as non-blocking; no behaviour and no schema changes. - packages/drivers/driver-turso/README.md — the README named both arms and the WebSocket gap but dropped the clause `turso-driver.ts`'s docblock and the changeset both carry: on the replica arm the native binding's own sync is not cancelled, only no longer awaited. Wording matched to the docblock. - docs/design/driver-turso.md §10 — "Both declare exactly the keys the driver reads" overstated: the package-published mirror does not declare `mode`, which `TursoDriver.detectMode` reads. The sentence now claims only the direction ADR-0049 governs (no declared key the driver does not read) and names the gap. The gap itself is pre-existing and deliberately left alone. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ADLdAs2pVcH17h9tZKWMBg
|
os-dev-report Resumed patch round for #16024. No push was made: the branch had already been advanced by another seat. Branch stateThe handoff worktree sat at
My only local-only commit is the now-redundant merge Substance unchangedThe three-dot diff of the live head against The 45-minute wall, answered on the live head
Check runs on
Shard 5/6 no longer meets the wall: 18m41s against 45:00. It clears even the old 30:00 value with an 11-minute margin, so the two cancellations this PR met at 30.3 min are gone. No standing-down note is needed and no re-run was requested. Local verification, run on
|
Fixes #16024
Clause-②: yes
Ruling (director seat, decision batch #60, 2026-09-06T15:25:55Z; maintainer reply verbatim 「同意」), per key:
TursoDriverConfig.timeoutforward,TursoConfigSchema.localPathremove,TursoConfigSchema.wasmremove. Not a rename for any of the three. This PR disposes of all three, so it closes the card.1. The verification the ruling handed over — what
@libsql/client'sConfigexposesRead in this worktree from
@libsql/core@0.17.4(lib-esm/api.d.ts; the package resolves@libsql/client@0.17.4,@libsql/hrana-client@0.10.0; manifest range^0.17.3):Config.timeout?: numberexists, documented verbatim as "Busy timeout in milliseconds for localfile:databases … It only takes effect for local SQLite databases; remote clients ignore it." That is a lock-contention busy timeout, not the operation timeout the driver's docblock promises. Forwarding to it would have kept remote mode exactly as inert as before and given replica mode a different setting under the same name. Not forwarded.Config.fetch?: Functionis the custom fetch for the HTTP client. On the client source:http.jshands it tohrana.openHttp(...), whoseHttpClientuses it for every request, the protocol-version probe (findEndpoint) included;ws.jsnever readsfetch; andsqlite3.js(thefile:+syncUrlembedded-replica arm) forwards onlyauthToken / encryptionKey / remoteEncryptionKey / syncUrl / syncPeriod / readYourWrites / offline / timeoutto the nativelibsqlDatabase—fetchnever reaches that arm.So the ruling's fallback ("wrap the client's
fetchwithAbortSignal.timeout(ms)on the remote and replica arms") lands as written on the remote HTTP arm and cannot land on the replica arm — there is no fetch there. On the replica arm the only remote operation the driver performs issync(), so the window is applied to the awaitedsync()itself (a race against the deadline; the native sync is not cancellable, only no longer awaited). Both arms now fail a stalled remote inside the configured window with the ADR-0112 envelopecode: TIMEOUT,status: 504.wss:///ws://remote URLs ride the WebSocket transport, which exposes no seam of either kind in 0.17.4; the docblock and README say so rather than promising it.2. What changed, per key
TursoDriverConfig.timeout(forward).turso-driver.ts: the two remotecreateClientsites (connect()and the transport's lazy connect factory) now go through one privatecreateRemoteClient(), which passesfetch: fetchBoundedBy(timeout)when the key is set and greater than 0 — the drift between two hand-copiedcreateClientcalls is howtimeoutsat unforwarded four lines from a forwardedconcurrency.fetchBoundedByaborts withAbortSignal.timeout, honours an upstream request signal viaAbortSignal.any, and translates only its own abort into the envelope.sync()is bounded byboundedBy(...)with the same envelope.0/ unset = no bound (what the published schema already documented). The key keeps its bare spelling: the datasource seam in@objectstack/service-datasourcealready maps the authoredconfig.timeoutMsontoTursoDriverConfig.timeout, citing this card as the reason the driver key was not renamed — so an authoredtimeoutMsthat used to be silently dropped now bounds the connection.TursoConfigSchema.localPathand.wasm(remove). Same treatment as this package'stimeout→timeoutMsrename: the shape is a plainz.object(STRIP posture), so a bare deletion would have stripped both keys in silence; they stay declared asz.never({ error })tombstones whose.describe()is[REMOVED] ...plus the prescription (tscrefuses them onTursoConfig; the parse raises the prescription naming what actually does the job —urlfor the replica file, the remote arm for a runtime without native bindings — and the standardizedos migrate meta --from 17sentence).timeoutMs's describe now says what the code does ("Operation timeout in milliseconds for remote operations (0 = no bound)"). No internal issue id in any customer-facing string.Docs following the code: README
:208docblock;docs/design/driver-turso.mdconfig table (dropped the two rows,timeoutrow →timeoutMswith the real behaviour, and corrected its claim that the schema lives only inpackages/spec— the package mirror is where these keys lived).ADR-0087: D3 semantic entry
driver-turso-config-local-path-wasm-retired(packages/spec/src/migrations/entries/semantic/18.…ts, registry regenerated withgen:migration-registry). Semantic rather than a D2 conversion because@objectstack/spec's own turso contract (data/TursoConfig, strict) never declared either key, so no stack source or stored datasource row that passed the spec door can carry them, and a value that never did anything has no lossless rewrite. Measured: semantic entries project intoregistry.tsonly — sibling idui-record-blocks-unknown-keys-refusedhas 0 hits inspec-changes.jsonand the upgrade guide, same as mine — socheck:generatedreporting all 15 artifacts current is a real reading, not a missed regeneration.Changeset:
@objectstack/driver-tursominor with the BREAKING banner and a Wrote / Write-instead migration table (the gate refusesmajor; the removal narrows a published config surface, so nothing lower thanminoris honest),@objectstack/specminor for the ledger entry (a new member of the published migration registry is an additive widening of that package's surface, and the gate's WHICH LEVEL rule sets the floor atminor— it waspatchat a0c4b39, corrected at 8c3dd7b), markeradr-0087: registered driver-turso-config-local-path-wasm-retired.3. Premises re-derived on
origin/main(f377394), not on the card's feature branchpackages/drivers/driver-turso/src/with tests excluded and comment lines stripped:concurrency(positive control) is declared atturso-driver.ts:91,spec/turso.zod.ts:95and forwarded at:433,:564,:585;timeout—turso-driver.ts:108declaration only;localPath—spec/turso.zod.ts:110declaration only;wasm—spec/turso.zod.ts:153declaration only (the one other hit,remote-transport.ts:3255, is the package namedriver-sqlite-wasminside a message string).localPathandwasm— key-shaped grep onpackages/spec/src/data/driver/turso.zod.tsreturns 0 for each, thetimeoutMscontrol returns:224. The spec side does declaretimeoutMs(+ a retiredtimeouttombstone) since [#14478 stack 5/6]data/·ui/·ai/·integration/: the 7 remaining duration keys carry their unit in the key name — ADR-0087 conversions with readers #15680, which is what makes the forward limb reach a real authored key.z.object: unknown keys STRIP, so a bare deletion would have been a silent narrowing; the tombstones make it a loud refusal by choice, which is the right shape. Blast radius, measured: the host-side driver registry (packages/spec/src/data/driver/config-registry.zod.ts:348/369) mapstursoto the spec-sideTursoConfigSchema/getTursoConfigJsonSchema— not the package's. In-repo consumers of the package export: none outside its own tests; objectui (../objectuiat the pinned checkout): none. So "the schema a host reads to render Turso configuration UI" is the spec-side one, which never had these keys; the removal reaches only direct npm consumers ofTursoConfigSchemafrom@objectstack/driver-turso.4. Contract review (clause 2) — measured on the built declarations
pnpm buildof the package at base (dist restored from the turbo cache,index.d.tssha7152306…) and at head (real rebuild:index.jsmtime 1788710110 → 1788711166,index.d.tsshafccf5bc…, 97.08 KB),index.d.tsandindex.d.mtsbyte-identical to each other on both sides. Diff of the published declaration, every hunk classified:TursoDriverConfig.timeoutJSDoc expanded — comment only, not surface.private createRemoteClient;added — a private member's bare-name line, not public surface.sync()JSDoc expanded — comment only.localPath: z.ZodOptional of z.ZodString→z.ZodOptional of z.ZodNever— declaration text moved.wasm: z.ZodOptional of z.ZodBoolean→z.ZodOptional of z.ZodNever— declaration text moved.The last two change what the published
TursoConfigSchemaaccepts:{ url, localPath }and{ url, wasm }are newly rejected, at compile time and at parse. That is a request newly rejected on a published contract, andtimeoutnewly changes runtime behaviour on a published constructor option ⇒ clause 2: yes, declared on the card's claim comment and carried byneeds:contract-reviewon both carriers. This round does not clear its own contract review.5. Tests
src/turso-driver-timeout.test.ts(5 cases): remote HTTP arm against a realhttp.Serverthat accepts the connection and never responds — withtimeout: 100thefindfails asTIMEOUT/ 504 naming the window, the server saw ≥ 1 request; negative control withouttimeoutis still pending after 1000 ms. Replica arm with a stub client whosesync()never settles —timeout: 100rejectsTIMEOUT/ 504; controls: notimeoutandtimeout: 0both still pending after 1000 ms. Firing control on the unmodified tree: the two positive cases were red (each hung to vitest's 5 s cap), the three controls green.src/spec/turso.test.ts: refusal-envelope pins forlocalPathandwasm(path,was removed, theurl/nothing selects a WASM buildclause,Delete the key,os migrate meta --from 17),wasm: falserefused too, tombstones absent from a parse that never wrote them; the two fixtures that authoredlocalPath/wasmrewritten to the shape the driver reads (they were the measurement: accepted, asserted, consumed by nothing).vitest run --maxWorkers=2→ 45 files, 1178 tests passed.typecheckexit 0, andtsc --noEmit --listFileslists all four edited/addedsrc/**files in the 51-file program (the package tsconfig includessrc/**/*), so the typecheck green covers the test files.6. Gates run locally — code/test/build union at a0c4b39; the changeset family re-run at the final head 8c3dd7b (
git rev-parse --short HEAD= 8c3dd7b; the two trees differ only in.changeset/driver-turso-inert-config-keys.md, the spec bumppatch→minor)Derived with
node scripts/pm/dispatch-gates.mjs(no path argument). Green:check:nul-bytes;check-adr-0087-registration --base origin/main("1 declared-breaking changeset(s), each carrying an ADR-0087 disposition") +--self-test;check-changeset-no-major;check-empty-changeset;check:changeset-gate-self-tests;check:doc-authoring;check:cross-package-test-inputs(117/117);check:published-files;check:test-source-alias;check:driver-conformance(driver-turso row all ok);check:query-options-erasure;check:spec-parsed-alias;check:where-matcher;check:objectql-double-limit;check:logger-receiver-detach;check:tenant-chokepoint;check:slot-lookup;check:org-identifier;check:page-declaration-shape;check:merge-driver;check:objectui-changeset;check:dts-closure(8 built packages swept — the driver-turso dependency closure — 46/46 declaration files present);check:type-source-resolution;check:adr-anchors;check:cross-package-test-inputs; system-context-census, comment-mask-adoption, keyed-text-bounds, platform-object-tenancy-census, plugin-teardown-shape, registry-log-declared, undeclared-dep-imports, ci-filter-parity, reference-carrier-shape, dev-prereqs, release-rehearsal-clone (each with--self-testwhere the workflow runs one); docs-auditcheck-affected-docs/check-drift-comment.pnpm --filter @objectstack/spec buildthencheck:generated: all 15 artifacts current; the derived spec source auditsduration-unit-keys(215 keys, 0 offenders),browser-reachable-entries,entry-nameability,exported-any,dual-source-exports,llms-txt,objectui-pin-citations,variant-docs,empty-state,liveness,strictness-ledger,skill-refsall green. Repo-widepnpm lint(eslint . --no-inline-config): exit 0 in 117 s — the full scan, not a narrowing.Not measured (exit 3 = prerequisite not met, CI owns them):
check:dual-build-cjs-loads(needs the whole workspace built;connector-mcpdist absent here) and@objectstack/lint check:doc-formula-expressions(needs@objectstack/lintbuilt).7. Out of scope, left alone
config.timeout, so a datasource authored with the canonicaltimeoutMsis dropped at the seam that builds the driver config #16023 untouched (its blocker is [#14478 stack 5/6]data/·ui/·ai/·integration/: the 7 remaining duration keys carry their unit in the key name — ADR-0087 conversions with readers #15680/[#14478 stack 6/6] widencheck:duration-unit-keysfrompackages/spec/src/**to every workspace package's zod schemas (folds #15642) and convert the one turso offender it finds #15682).wss:///ws://) has no timeout seam in@libsql/client0.17.4 — documented on the key, filed separately as a finding.Authored in Claude Code session
session_01ARYe3yQTQCUFm5qPYNgKaJ, branchclaude/issue-16024-turso-inert-config-keys.Generated by Claude Code