Repository navigation
[ADR-0049] @objectstack/driver-turso publishes three config keys nothing reads — timeout on the TS interface, localPath and wasm on the zod schema #16024
Description
Activity
- addedenhancementNew feature or requestNew feature or requestpriority:p2Medium: important, M3Medium: important, M3
on Sep 6, 2026 分诊 ·
domain:engine/enhancement/priority:p2/needs-user-decision分诊席位。⛔ 不认领、不派发、不写代码、不合并、不裁决 decision-box 卡。
origin/main@932acc3d,2026-09-06T04:37Z。三个键复现(行号有漂移)
键 卡写 实测 TursoDriverConfig.timeoutturso-driver.ts:108✅ timeout?: number;,上方 docblock 逐字:「Effective in replica and remote modes.」TursoConfigSchema.localPathspec/turso.zod.ts:110:94—localPath: z.string().optional().describe('Local file path for embedded replica')TursoConfigSchema.wasmspec/turso.zod.ts:153:111—wasm: z.boolean().optional().describe('Use WASM build for edge/browser environments')⚠️ 卡的关键对照「spec 侧一个都不声明」—— 我验了,成立,但路上有个陷阱裸 grep
localPath|wasm在packages/spec/src/data/driver/turso.zod.ts返回 2 条,看起来像是「spec 侧其实声明了」。逐条读::37 * existence of the CONTRACT — `mongodb` and `sqlite-**wasm**` are optional installs :255 * same reason `sqlite-**wasm**` does. Both are constructible and both have a⇒ ⭐ 两条都是散文,而且都是
sqlite-wasm这个包名撞上了wasm这个子串。localPath在该文件 0 命中,wasm作为键也是 0。⇒ 卡的断言成立:spec 侧的
packages/spec/src/data/driver/turso.zod.ts三个键一个都不声明。⛔ 但复算的人要按键读而不是按词读(纪律⑥,本轮第 N 次)。⭐ 卡最有力的论证,我加重
spec 侧的头注自陈它已经对这个面做过 ADR-0049:
the keys there are "exactly the
TursoDriverConfigfields the driver reads and that an author can express as data", explicitly because "a key declared here that no driver consults would be a new inert slot, and this file exists to close one."⇒ ⭐ 同一个平台上,同一个驱动的两份 schema,一份做过 enforce-or-remove 并因此更小,另一份没做。 这不是「有个键没人读」,是两份契约对同一个驱动给出不同的可授权面。
而且卡指出了后果:包自己的那份 schema 是宿主用来渲染 Turso 配置 UI 的那一份 ⇒ 这三个键正被呈现给管理员填写。
定级 p2
- 三个键被呈现给管理员(配置表单),填了什么都不会发生;
timeout的散文承诺(README:208逐字重复)与concurrency(声明在它上方四行,且确实被转发)并排 ⇒ ⭐ 同一段配置里,一个生效一个不生效,外观完全一样;wasm的承诺尤其误导:一个 edge/browser 部署以为自己选了 WASM 构建,实际拿到的是import('@libsql/client')解析到的任何东西。
不给 p1:无数据损坏、无越权;填了无效键的后果是「没有效果」,不是错误行为。
为什么是
needs-user-decisionADR-0049 的两个出口逐键都可能不同,卡说得对:
Per key, one of: forward it(
timeout转发到 client 的操作超时是一个小改动,而且正是散文已经承诺的), or remove it with the tombstone/ADR-0087 treatment #15682 used for the rename。⇒ 三个键很可能不是同一个答案:
timeout有现成的转发目标(散文已承诺、concurrency就在旁边示范),localPath与 replica 臂用url命名本地文件的既有设计冲突,wasm则需要一个「选择 WASM 构建」的机制而那东西不存在。⇒ 逐键裁。⛔ 一条硬边界,卡划了,我加重
⛔ Not a rename — a rename would leave an inert key wearing a better name, which is the outcome ADR-0049 exists to prevent。
⇒ ⛔ 接卡人不要把
timeout改成timeoutMs就结案。#15682 刻意没有碰这三个键,正是因为「重命名一个惰性键等于把它批准为真的」。车道
domain:engine落点
packages/drivers/driver-turso⇒ 车道表drivers/driver-*在 engine 行。相邻
#16023(本轮我已路由,判
pm:blockedon #15680)—— 它引用本卡作为「timeout从未被转发」的依据,那条依据成立。⚠️ 反过来,#16023 的前提(spec 侧已改名timeoutMs)在main上还不成立——我在那边测了:packages/spec/src/data/driver/turso.zod.ts:215仍声明timeout。⇒ 两张卡都在等 #15680/#15682 落地,⛔ 但本卡不依赖它们(这三个键与那次改名无关),所以本卡不 blocked。
Generated by Claude Code
Ruling recorded — per key (director seat, decision batch #60, 2026-09-06)
Maintainer reply, verbatim: 「同意」 (all five batch #60 recommendations adopted).
key ruling how TursoDriverConfig.timeoutforward Verify first whether @libsql/client'sConfigexposes a native operation timeout (the director could not check it in-container). If it does, forward to it. If it does not, implement the promised behaviour driver-side: wrap the client'sfetchwithAbortSignal.timeout(ms)on the remote and replica arms. Either way the key gains real behaviour, with a test that a stalled remote fails within the configured window.TursoConfigSchema.localPathremove Conflicts with the shipped design (the replica arm names the local file via url); forwarding would create a second way to say the same thing. Tombstone / ADR-0087 treatment as #15682 used.TursoConfigSchema.wasmremove No mechanism selects a WASM build; forwarding would mean building one. Same removal treatment. ⛔ Not a rename for any of the three (an inert key with a better name is what ADR-0049 exists to prevent). README
:208and the package schema's.describe()strings follow the code. #16023 is unaffected (its blocker is #15680/#15682, not this card).Labels:
needs-user-decision→pm:queue. Ledger on #12708 (batch #60).
Generated by Claude Code
Claim: os-dev round for #16024 (domain:engine seat, CONTRACT_REVIEW_TIER, dispatched on the 2026-09-06T15:25:55Z ruling)
Session:session_01ARYe3yQTQCUFm5qPYNgKaJ
Branch:claude/issue-16024-turso-inert-config-keys
Clause-②: yesScope per the ruling:
TursoDriverConfig.timeoutgains real behaviour (native@libsql/clientoption verified first, otherwise driver-sideAbortSignal.timeouton the remote and replica arms, with a stalled-remote test);TursoConfigSchema.localPathandTursoConfigSchema.wasmare removed with the tombstone / ADR-0087 treatment #15682 used. Not a rename for any of the three.Contract-review declaration, added after the built-declaration measurement (PR #16376, head a0c4b39): exported-name set of
dist/index.d.ts28 → 28 (added none, removed none), but two member-level declaration hunks moved —TursoConfigSchema.localPathand.wasmwent fromZodOptionalofZodString/ZodBooleantoZodOptionalofZodNever— so{ url, localPath }and{ url, wasm }are newly rejected on a published contract, andtimeoutnewly changes runtime behaviour on a published constructor option. Hence yes;needs:contract-reviewhung on both carriers; this round does not clear its own review.
Generated by Claude Code
- added a commit that references this issue
on Sep 6, 2026 os-dev-report
{ "issue": 16024, "status": "done", "branch": "claude/issue-16024-turso-inert-config-keys", "pr": "https://github.com/objectstack-ai/objectstack/pull/16376", "premise_still_valid": true, "summary": "All three keys disposed of per the 15:25:55Z ruling, PR #16376 left as a green draft with needs:contract-review on both carriers; nothing released, no label cleared. Ruling 1.4 verified first: @libsql/core@0.17.4 Config.timeout is the BUSY timeout for local file databases ('remote clients ignore it'), not an operation timeout, so it is deliberately not forwarded; Config.fetch is the only remote seam and it reaches ONLY the HTTP transport (http.js hands it to hrana; ws.js never reads it; sqlite3.js, the replica arm, passes no fetch to the native libsql Database). So the fallback lands as written on the remote HTTP arm (fetch wrapped with AbortSignal.timeout, upstream signal honoured via AbortSignal.any, abort surfaced as the ADR-0112 envelope code TIMEOUT / status 504) and CANNOT land on the replica arm — there the driver bounds the awaited sync() by a race with the same envelope (the native sync is not cancellable, only no longer awaited); wss:// and ws:// remote URLs have no seam of either kind and the docblock/README now say so (filed as #16378). Two remote createClient sites folded into one createRemoteClient() so the arms cannot drift apart again. localPath and wasm are z.never tombstones on the package schema (plain z.object = STRIP posture, so a bare deletion would have been silent), .describe() = [REMOVED] + prescription pointing at url / the remote arm, standardized migrate sentence, no issue ids in customer-facing text; timeoutMs describe now states the real behaviour; README :208 and docs/design/driver-turso.md table follow the code. ADR-0087: D3 semantic entry driver-turso-config-local-path-wasm-retired (spec-side turso contract never declared the keys, so no stored row past the spec door can carry them and there is no lossless rewrite); measured that semantic entries project into registry.ts only (sibling id 0 hits in spec-changes.json / upgrade guide, same as mine), so check:generated 'all 15 current' is a real reading. Changeset: driver-turso minor + BREAKING banner + Wrote/Write-instead table, spec patch for the ledger, marker adr-0087: registered. Zone 2 by number — 2.1 partly FALSIFIED: exported-name set 28 to 28, ADDED and REMOVED both empty, because the keys are members of one exported schema type, not exports; the removal shows one level down as two member-level declaration hunks (ZodOptional of ZodString/ZodBoolean to ZodOptional of ZodNever) = requests newly rejected, hence Clause-② yes anyway. 2.2 holds (minor). 2.3 holds, re-derived on f377394ae with the concurrency control firing at :433/:564/:585. 2.4 holds for localPath/wasm (key-shaped 0 hits each, timeoutMs control at :224); the spec side does declare timeoutMs. 2.5: package schema is non-strict (STRIP), so the tombstone turns a silent narrowing into a loud refusal by choice; and the triage premise 'the schema a host renders Turso UI from' is FALSIFIED — config-registry.zod.ts:348/369 maps turso to the SPEC-side schema, and the package export has zero in-repo (outside its own tests) and zero objectui consumers, so blast radius is direct npm importers of TursoConfigSchema from @objectstack/driver-turso. Clause-② self-review statement (1.6): my determination is a measured declaration, NOT a self-reviewed clearance — this round does not clear its own contract review. Assignee was empty at dispatch (PM-side half state); per the os-dev rules I did not write it, which conflicts with the dispatch prompt's 'assign yourself' — flagged, not silently resolved. PR body carries NO hand-written footer (prompt 1.7 measured correction); the platform appended exactly one session-form footer, read back intact (12875 sent, 12965 stored = src + footer). One operational note, not a finding: running gate self-tests with NODE_USE_ENV_PROXY=1 reds the 'importing this module prints NOTHING' cases (check:cross-package-test-inputs, check:merge-driver) because node prints the UNDICI warning on import; both are 117/117 and green without the flag. PM relay mid-round: `Check Changeset` was red at a0c4b39df on two coupled defects and both are repaired at 8c3dd7b39 by way 1 (the declaration stands): (1) `@objectstack/spec` raised `patch` to `minor` — the WHICH LEVEL text read first-hand (pr-automation.yml:667-682) says: 'A purely additive widening of a published package's public surface (a new exported symbol on an index, a new accepted key or value) takes at least minor. The commit type may raise a bump but never lower it below what the act requires; a fix( that widens an index is therefore minor, and a fix( that changes no public surface stays patch. During the launch window major stays refused by check-changeset-no-major and breaking-ness is carried by the BREAKING banner plus the ADR-0087 disposition, not by the level. Ruled by the maintainer on 2026-09-04 (decision batch #35) on #15294 … This is prose, not a gate -- no check computes it; check-changeset-no-major.mjs refuses major and says why in its header, and the two remaining levels are yours.' Two readings for the PM: the new registry.ts entry is an additive widening of spec's published surface so minor is the floor and, with major refused, the only level; and the prose's own 'no check computes it' is now stale — check-changeset-no-major.mjs's LEVEL AXIS (judgeLevel/renderLevel, ~:940-1030) does compute the coupled case clause-② yes + patch on a package whose packages/*/src/** the PR moves, which is exactly what fired. (2) the bare `Clause-②: yes` line added to the PR body (stored body's platform footer stripped before the PATCH, then re-appended by the platform exactly once, read back). Level axis reproduced locally with the real posted body and labels via --event: '✓ LEVEL AXIS: this PR declares clause-② yes, and no package whose packages/*/src/** it moves is graded patch · carrier: needs:contract-review IS on this PR · declaration line: Clause-②: yes'. Labels re-read on both carriers after the push: needs:contract-review still on #16376 and #16024 (no clear had happened, so no re-hang was owed).", "tests": "Firing control on the UNMODIFIED tree (f377394ae): new src/turso-driver-timeout.test.ts ran 2 failed | 3 passed — both positive cases hung to vitest's 5 s cap (the promised bound absent), the three negative controls green. After the change: the same file + src/spec/turso.test.ts = 'Test Files 2 passed (2) / Tests 37 passed (37)'; whole package at a0c4b39df via os-verify-lock: 'Test Files 45 passed (45) / Tests 1178 passed (1178)', VITEST_EXIT=0 (exit captured by redirect, not pipe). The remote positive case uses a REAL http.Server that accepts and never responds (server saw >= 1 request; find fails TIMEOUT/504 naming '100 ms' and 'TursoDriverConfig.timeout'); the replica case a stub client whose sync() never settles; controls: no timeout and timeout: 0 still pending after 1000 ms. Tests import ./turso-driver from src, so no dist ablation applies; the clause-② instrument used real rebuilds (base dist restored from turbo cache, index.d.ts sha 7152306...; head real tsup run, index.js mtime 1788710110 -> 1788711166, index.d.ts sha fccf5bc...) and every hunk classified (2 JSDoc-only, 1 private bare-name line, 2 declaration moves). pnpm --filter @objectstack/driver-turso typecheck exit 0; tsc --noEmit --listFiles lists turso-driver.ts, spec/turso.zod.ts, spec/turso.test.ts and turso-driver-timeout.test.ts in the 51-file program (TSC_EXIT=0). Gate union at a0c4b39df (git rev-parse --short HEAD = a0c4b39df; no commit after it), derived by node scripts/pm/dispatch-gates.mjs with no path argument: green — check:nul-bytes ('75 assertions'), check-adr-0087-registration --base origin/main ('1 declared-breaking changeset(s), each carrying an ADR-0087 disposition') + --self-test (332), check-changeset-no-major ('introduces no major bump'), 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 package(s) swept - 46/46' = the driver-turso dependency closure built in this worktree, driver-turso dist included), check:type-source-resolution, check:adr-anchors, system-context-census ('105 elevation read sites ... all anchored'), 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 --self-test, release-rehearsal-clone --self-test, docs-audit check-affected-docs and check-drift-comment; pnpm --filter @objectstack/spec build then check:generated ('All 15 generated artifacts are up to date', CHECK_GENERATED_EXIT=0) and the derived spec source audits duration-unit-keys ('215 duration-shaped numeric key(s) ... all carry their unit'), browser-reachable-entries, entry-nameability, exported-any, dual-source-exports, llms-txt, objectui-pin-citations, variant-docs, empty-state, liveness, strictness-ledger, skill-refs. Repo-wide pnpm lint (eslint . --no-inline-config) exit 0 in 117 s — full scan, no narrowing. NOT MEASURED (exit 3 = prerequisite not met, CI owns): check:dual-build-cjs-loads (connector-mcp dist absent) and lint check:doc-formula-expressions (@objectstack/lint not built). check-clause2-carriers.mjs --pair 16376 (NODE_USE_ENV_PROXY=1, redirect-then-read): CLAUSE2_EXIT=0 — 'the clause-② declaration is readable in the fixed spelling and both carriers agree'; checker blob 751b4a6e... identical to origin/main@455d0372f's. Labels: needs:contract-review added additively to #16376 and #16024, comparative read-back after == union(before, target) on both, nothing stripped. Claim comment 5560324256 PATCHed with bare 'Clause-②: yes' on its own line, read back (one footer). After the changeset repair: git diff --stat a0c4b39df..8c3dd7b39 = exactly 1 file (.changeset/driver-turso-inert-config-keys.md, +4/-2), so the code/test/build union above stands for 8c3dd7b39 and the changeset family was re-run at 8c3dd7b39 without the proxy flag: check:nul-bytes ✓, check-adr-0087-registration --base origin/main ✓ ('1 declared-breaking changeset(s), each carrying an ADR-0087 disposition'), check-empty-changeset ✓, check:changeset-gate-self-tests ✓ (157 assertions), check-changeset-no-major --base origin/main ✓ and, with --event built from the real PR payload, LEVEL_AXIS_EXIT=0 with the ✓ LEVEL AXIS line. check-clause2-carriers.mjs --pair 16376 re-run on the repaired pair: CLAUSE2_EXIT=0, 'the clause-② declaration is readable in the fixed spelling and both carriers agree' (checker blob identical to origin/main@455d0372f).", "mcp_calls": "2 — both search_issues (the declared dedup for the WebSocket-arm finding, which returned #16023 and closed #5602, neither a duplicate; and its firing control, which hit #16024). Everything else went over REST through the proxy.", "open_questions": [ { "question": "The ruling's fallback names 'the remote and replica arms' for the fetch wrap; measured, the replica arm has no fetch (native libsql binding), so the window there is a race on the awaited sync() that cannot cancel the native sync. Is that the accepted shape for the replica arm, or should the replica arm's timeout be narrowed to remote mode only?", "options": [ "A: keep the sync() race (landed) — the promise the docblock made ('effective in replica and remote modes') is delivered on the only remote operation that arm performs", "B: drop the replica bound and document timeout as remote-mode only", "C: forward to Config.timeout on the replica arm as well (rejected here: that is the local busy timeout, a different setting)" ], "recommendation": "A, because the stalled-remote test the ruling asked for passes on both arms with the same envelope, and B would leave the docblock's replica promise inert again." }, { "question": "ADR-0087 registration shape for a removal from a package-published mirror schema whose keys the spec contract never declared: a D3 semantic entry (landed) versus a D2 stripKeys conversion.", "options": [ "A: D3 semantic entry (landed) — no stored row past the spec door can carry the keys, so there is nothing lossless to rewrite", "B: add a D2 conversion with stripKeys on turso datasources for defence in depth" ], "recommendation": "A, because a conversion whose fixture can never occur on a spec-valid source is an untestable entry; if the maintainer wants B it is a small follow-up on the same ledger." } ], "out_of_scope_findings": [ "filed as #16378: `TursoDriverConfig.timeout` has no seam on the WebSocket transport (wss:// / ws://) in @libsql/client 0.17.4 — documented as unbounded on the key; label finding, unassigned, options a-d listed for triage" ] }
Generated by Claude Code
- added a commit that references this issue
on Sep 6, 2026 Round record — PR #16376. The round verified the ruling's own premise and found it false, and it falsified this card's blast-radius sentence too
domain:enginedispatching seat, 2026-09-06T16:5xZ. Dispatched atCONTRACT_REVIEW_TIER(fable) because the content — two keys leaving a published schema, a third gaining behaviour — is clause-② by the ruled criterion, whatever--tier <paths>says.⭐⭐ The verification step the ruling handed over came back NO
The ruling said: "Verify first whether
@libsql/client'sConfigexposes a native operation timeout (the director could not check it in-container). If it does, forward to it. If it does not, implement the promised behaviour driver-side." Measured on@libsql/core@0.17.4:Config.timeoutis the BUSY timeout for local file databases — "remote clients ignore it" — not an operation timeout.⇒ the forward branch is unavailable and the
AbortSignalfallback lands as the ruling's second limb prescribed. ⭐ This is the value of writing a verification step into a ruling rather than a conclusion: the director could not run it, said so, and the branch that fired is the one the measurement chose.⭐⭐ And the fallback could not land where the ruling assumed
The ruling named "the remote and replica arms". Measured:
Config.fetchreaches only the HTTP transport —http.jshands it to hrana,ws.jsnever reads it, andsqlite3.js(the replica arm) passes no fetch to the native libsqlDatabase. ⇒ on the replica arm there is no fetch seam at all; the driver instead bounds the awaitedsync()by a race carrying the same envelope.⚠️ Stated honestly by the round rather than glossed: the native sync is not cancellable — the race stops awaiting it, it does not stop the work. The docblock and README now say so.⚠️ And a third arm has no seam of either kind:wss:///ws://remote URLs. Filed as #16378 rather than left implied.Ruled on Q1 — A, keep the
sync()race. The promise the docblock made is "effective in replica and remote modes"; option B (remote-only) would leave that promise inert again, which is the exact ADR-0049 defect this card exists to close. The stalled-remote test the ruling demanded passes on both arms with the same envelope. ⛔ Option C is refused on the round's measurement, not on taste:Config.timeoutis a different setting.Ruled on Q2 — A, the D3 semantic entry stands. ⭐ The argument that decides it: "a conversion whose fixture can never occur on a spec-valid source is an untestable entry." A D2
stripKeysconversion here would claim a coverage it could never demonstrate, and an untestable migration entry is worse than none — it reads as protection to the next person. If the maintainer wants defence in depth, B is a small follow-up on the same ledger.⛔ This card's own blast-radius sentence is FALSIFIED
The card says the package schema "is the schema a host reads to render Turso configuration UI, so these three are offered to an admin in a form." Measured:
config-registry.zod.ts:348/369maps turso to the spec-side schema, and the package export has zero in-repo consumers outside its own tests and zero objectui consumers. ⇒ the blast radius is direct npm importers ofTursoConfigSchemafrom@objectstack/driver-turso— real, but not the admin-facing form the card pictured. ⭐ Recording it because that sentence was load-bearing in the card's "why this is worth a card" section, and the card is right for a different reason than the one it gave.⭐ The removal is a LOUD refusal by choice, not a silent one
The package schema is a plain
z.object⇒ STRIP posture. A bare deletion oflocalPath/wasmwould therefore have been silent — the keys would simply vanish from parsed output. The round usedz.nevertombstones with[REMOVED]+ a prescription pointing aturl/ the remote arm, which turns a silent narrowing into a refusal that names itself. ⛔ That distinction is not cosmetic and would have been easy to miss.⛔ My Zone 2.1 was falsified — the same blindness, a second time in one session
I predicted the removal would show as a non-empty REMOVED set. It does not: exported names 28 → 28, ADDED and REMOVED both empty, because the keys are members of one exported schema type, not exports. The removal appears one level down, as two member-level declaration hunks (
ZodOptional<ZodString>/ZodOptional<ZodBoolean>→ZodOptional<ZodNever>) — requests newly rejected, henceClause-②: yesregardless.⭐ This is the second measurement today of the same instrument gap (the first was #16057's
260 → 260while two members were added). The exported name set answers "did an export move", ⛔ never "did the surface move". Both readings are owed, every time.The
WHICH LEVELprose, read first-hand as I asked — and it is stale about itselfI relayed the changeset rule second-hand and told the round to read the original. It did (
pr-automation.yml:667-682) and reports the operative sentence — "A purely additive widening of a published package's public surface … takes at leastminor. The commit type may raise a bump but never lower it below what the act requires" — plus a correction I could not have made: the prose says "This is prose, not a gate — no check computes it", and that is no longer true.check-changeset-no-major.mjs's LEVEL AXIS (judgeLevel/renderLevel) does compute the coupled case clause-② yes +patchon a package whosepackages/*/src/**the PR moves — which is exactly what fired here. ⇒ the repo's own guidance describes a state of affairs the repo has since outgrown.Both defects were repaired by way 1 (
@objectstack/specpatch→minor; the bareClause-②: yesline added to the PR body), ⛔ not by lowering the declaration to fit the level — which is what the gate's own warning forbids.⚠️ An operational side effect of my own standing adviceRunning the gate self-tests with
NODE_USE_ENV_PROXY=1reds the "importing this module prints NOTHING" cases (check:cross-package-test-inputs,check:merge-driver), because node emits the UNDICI experimental warning on import. Both are 117/117 green without the flag. ⇒ the transport flag is for API-reading scripts only; ⛔ do not blanket it across gate runs.State
PR #16376: green draft,
needs:contract-reviewon both carriers (comparative read-back, nothing stripped),--pair 16376exit 0 with the checker blob verified againstorigin/main. ⛔ Not released. Filed: #16378.
Generated by Claude Code
- added a commit that references this issue
on Sep 9, 2026 - added a commit that references this issue
on Sep 28, 2026
Surfaced while implementing #15682 (stack card 6/6 of #14478), which renamed this package's
TursoConfigSchema.timeouttotimeoutMs. That card deliberately did NOT touch the keys below: under ADR-0049 the disposition for a declared-but-unenforced key is enforce or remove, and renaming an inert key would ratify it as real. Filed bare for triage.What was measured, on
claude/issue-15682-widen-duration-unit-keys-populationTursoDriverbuilds its@libsql/clientin three arms (turso-driver.ts:429,:560,:579). Those arms forwardurl,authToken,encryptionKey,concurrencyand the sync URL. A repo-wide grep for each key below overpackages/drivers/driver-turso/src/, tests excluded, returns only the declaration itself:TursoDriverConfig.timeoutsrc/turso-driver.ts:108createClientTursoConfigSchema.localPathsrc/spec/turso.zod.ts:110TursoConfigSchema.wasmsrc/spec/turso.zod.ts:153Each declaration advertises a behaviour in prose that no code delivers:
timeout— "Operation timeout in milliseconds for remote operations. Effective in replica and remote modes." The README repeats it verbatim at:208.concurrency, declared four lines above it, IS forwarded — so the two sit side by side and only one of them does anything.localPath— "Local file path for embedded replica." The replica arm names the local file withurl, which is what the driver's own doc comment and the spec-side contract both say.wasm— "Use WASM build for edge/browser environments." Nothing selects a WASM build; a browser/edge deployment gets whateverimport('@libsql/client')resolves to.Why this is worth a card rather than a comment
packages/spec/src/data/driver/turso.zod.tsdeclares NONE of the three, and says why in its header: the keys there are "exactly theTursoDriverConfigfields the driver reads and that an author can express as data", explicitly because "a key declared here that no driver consults would be a new inert slot, and this file exists to close one." So the spec contract already applied ADR-0049 to this surface and came out at a smaller set. The package's own published schema never did — and it is the schema a host reads to render Turso configuration UI, so these three are offered to an admin in a form.The decision this needs
Per key, one of: forward it (
timeoutto the client's operation timeout is a small change and is what the prose already promises), or remove it with the tombstone/ADR-0087 treatment #15682 used for the rename. ⛔ Not a rename — a rename would leave an inert key wearing a better name, which is the outcome ADR-0049 exists to prevent.Related: #14478 · #15682 · ADR-0049