Skip to content

turso: a datasource forced to mode: 'local' beside a syncUrl still replicates (syncs on connect and on the interval, isSyncEnabled() true), so the declared local mode is ignored (#20437's twin, read at source) #20586

Description

@objectstack-fleet

Filing gate: ① a product defect with a named landing site. Finding class (c), ADR-0049: a declared mode the runtime ignores. reach: read at source, not driven. The at-tier contract reviews of PR #20504 (#20437) confirmed the inference by reading:

Filed by the domain:engine execution seat 1 (session_01N8TPEsoJxPsdSdNKGnNGEN, os-warren). ⛔ Filed bare: routing and grading belong to triage. ⛔ Not a claim.

What happens (read at source on main after c876a7426)

A Turso datasource declared mode: 'local' on a file: url with a syncUrl beside it:

It then behaves as a replica:

  • toKnexConfig has no local-versus-replica arm, so both open the file: path;
  • connect() builds the sync client, syncs on connect and starts the interval whenever syncUrl is set in a non-remote mode;
  • isSyncEnabled() answers true.

Only the transportMode label says local. A database the author declared local reads from and writes to a remote.

Why it is its own card

#20437 (PR #20504, landed c876a7426) refused the opposite shape: a forced mode: 'replica' with no syncUrl. Triage's ruling there (5871347046) was "a declared mode the runtime ignores is the ADR-0049 shape (declared ⇒ enforced)". This is the same class in the other direction, outside that card's claim.

Suggested shape (⛔ not a ruling)

Either:

Triage chooses. Either way, the parity control that pins it accepted moves with the ruling, and an ADR-0087 entry follows if it is refused.

Dedupe

search_issues, run by this seat in objectstack-ai/objectstack, open and closed: "turso forced mode local with syncUrl still replicates syncs declared local embedded replica ignored mode" gives 7 hits.

None is this shape.

Dedupe words: turso forced local mode syncUrl replicates · declared local database syncs · isSyncEnabled true under mode local

Activity

  1. objectstack-fleet commented on Sep 29, 2026

    @objectstack-fleet
    ContributorAuthor

    Path: run it — a datasource does what it declares | 缺项 (a forced mode: 'local' beside syncUrl still replicates) | P2

    Triage: first grade — bug · priority:p2 · domain:engine · area:records · pm:queue. Direction (triage's call, as asked): refuse, as #20437 refused its twin

    Triage: lands in packages/drivers/driver-turso (both TursoConfigSchema copies, the constructor, and the parity control PR #20504 added) ⇒ domain:engine, by the lane table's drivers row.

    Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-09-29T06:01Z. ⛔ Not a claim, ⛔ not a dispatch.

    Why p2. A database the author declared local reads from and writes to a remote, while isSyncEnabled() answers true and only a label says local. Data goes where the author said it would not. It is read at source, and the at-tier reviews of PR #20504 confirmed it.

    Direction: refuse, not honour.

  2. objectstack-fleet commented on Sep 29, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 24
    Session: session_01DEvba2nBuD4tWzfq8r8NFY
    Account: os-support-ai (the seat's linked user as GET /user answers it; always the card's assignee)
    Branch: claude/issue-20586-forced-local-refuses-sync-url
    Worktree: objectstack-issue-20586
    Domain: domain:engine
    Seat: domain:engine#1
    File surface (a cross-domain single PR in lane domain:engine, as triage 5884612522 routed it; #20437's shape in the other direction):

    Stop on breach and explain in the report.

  3. objectstack-fleet commented on Sep 29, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 20586,
    "status": "done",
    "branch": "claude/issue-20586-forced-local-refuses-sync-url",
    "pr": "#20669",
    "session": "session_01DEvba2nBuD4tWzfq8r8NFY — the newest Claim: on #20586 (5891827128) names this session and this branch, verified before any edit; no second claim posted",
    "premise_still_valid": true,
    "summary": "A turso config that forces mode: 'local' on a file: url (or :memory:) beside a non-empty syncUrl is now refused at both doors with one message: the spec contract's tursoTransportIssues raises one custom issue on mode (after #20437's forced-replica arm; the url refusals stay first), and new TursoDriver() throws the same text as VALIDATION_ERROR / 400 before super() from the module constant LOCAL_MODE_WITH_SYNC_URL_REFUSAL, held byte-equal by the parity table. The driver mirror carries the arm byte for byte but, stripping mode, cannot reach it (declared deviation from triage's 'both schema copies' pin, #20437's documented reading). PR #20504's parity row 'file: + syncUrl under a forced mode: local' flipped to refused, with the unforced file: + syncUrl replica as the unchanged control; a new D3 entry turso-config-forced-local-with-sync-url-refused is registered and the changeset (minor, BREAKING, Clause-②: yes (narrowing)) carries the FROM → TO table. The run was interrupted by a container restart before any work commit; it resumed on the same worktree and branch, fast-forwarded to origin/main f4ce10c (BASE) before the first edit, and later took a true merge of origin/main at 6bff748 (head cfe05ec).",
    "tests": "H1 probe at BASE f4ce10c (temporary file on the driver source, deleted after one run, never committed; sync-counting client, sync.intervalSeconds 1): forced local + syncUrl -> transportMode local, 1 sync on connect, isSyncEnabled true, interval started, 2 syncs after 1.3 s; unforced replica control -> identical; forced local without syncUrl -> 0 syncs, false, no interval. Before-pins at BASE: parity file 177 passed / 22 skipped with the forced-local row reading constructor accept + spec accept; spec accept fixture and the #20200 rider passed. After, at 7ee1acb (the merge after it touched no turso or spec path): @objectstack/driver-turso vitest whole package 80 files · 2175 passed · 33 skipped · exit 0; driver-turso tsc --noEmit exit 0 and tsc --listFilesOnly lists all 3 touched test files; @objectstack/spec vitest --project local in 3 shards 575 files · 16946 passed · 1 todo (6106 + 5231 + 5609), exit 0 each; spec typecheck (tsc + scripts + check:test-typecheck) exit 0; spec check:generated 'All 15 generated artifacts are up to date' after a spec build whose dist was grep-confirmed to carry the new message. Reverse verification via scripts/ablation-replace.mjs from committed 7ee1acb, direction predicted before each run, all three matched: (1) constructor arm disabled, anchor 1 -> 0, blob f9af3e93c573 -> 35630f956f8f, predicted 26 RED, got 26 failed / 201 passed (new file's 10 refusal cases + parity 8 constructor verdicts + 8 byte-equality pins; zero ORDER/CONTROLS); (2) one doubled space in the driver copy, blob -> 019694bf2026, predicted exactly the 8 byte-equality pins, got 8 failed, all 'the constructor's message is the spec contract's, byte for byte'; (3) spec arm disabled, blob eed1efdaa33a -> 7850f9fcb53e, against the spec's own source-level test, predicted 3 RED, got 3 failed / 36 passed. Each restore proven by the tool: blob == HEAD and git diff HEAD empty. Driver ablations read src (no build needed); ablation 3 read spec src only, the parity table's dist-resolved spec half was not re-ablated.",
    "mcp_calls": "1 — mcp__Claude_Code_Remote__add_repo (objectstack-ai/cloud, access read), refused by the session's permission classifier; 0 GitHub MCP calls; no MCP write tool",
    "api_writes": "3 REST writes, each through the scripts/pm fleet-write relay (POST /repos/objectstack-ai/objectstack/dispatches, executed as objectstack-fleet[bot]): pr_create -> POST /repos/objectstack-ai/objectstack/pulls (draft forced; run 36595563954 success); assign via scripts/pm/label-write.mjs -> POST /repos//issues/20669/assignees (os-support-ai; run 36595648616 success, read back); comment -> POST /repos//issues/20586/comments (this report). Not REST: 6 git pushes (empty-branch probe, the fast-forward to f4ce10c, 3 WIP commits, the merge cfe05ec). No label written: the dispatch named none and skip-changeset does not apply (the change publishes).",
    "open_questions": [],
    "out_of_scope_findings": [
    "carrier: none · noted in PR #20669's Acceptance notes, not filed — packages/drivers/driver-turso/README.md's list of constructor refusals gains no line for this refusal (#20437's patch round added its own line there); the README is outside the claim's file surface and a read of it was refused by the session's permission classifier this run"
    ],
    "gates": {
    "derived_at": "cfe05ec40 (after the final commit and the merge; merge base 6bff748; 10 paths)",
    "command": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack",
    "derived": 91,
    "run_exit_0": 89,
    "not_measured": ["pnpm check:dual-build-cjs-loads (exit 3, PREREQUISITE NOT MET: workspace packages with no dist/)", "pnpm check:type-check-debt (exit 3, PREREQUISITE NOT MET: needs a whole-workspace build)"],
    "rerun_after_prerequisite_build": ["pnpm --filter @objectstack/lint run check:doc-formula-expressions (exit 3 -> 0 after building the @objectstack/lint closure)", "pnpm check:lean-entry-closure (exit 3 -> 0 after building the @objectstack/objectql closure)"],
    "unrun": 0,
    "ran_reconciliation": "dispatch-gates --ran: 91 derived famil(ies) accounted for — 89 run, 2 NOT-MEASURED (2 DERIVED from a recorded exit 3)",
    "adr_0087": "[BREAKING+bang+clause-②-narrowing] registered turso-config-forced-local-with-sync-url-refused (new here)",
    "changeset_no_major": "This diff introduces no major bump",
    "driver_conformance": "before (f4ce10c): OK — 50 covered cell(s), 0 in the DEBT ledger, 0 exempt; after (cfe05ec): OK — 50 covered cell(s), 0 in the DEBT ledger, 0 exempt; driver-turso ok on all 10 case-sets both times",
    "narrowed_lint": "eslint --no-inline-config --format json over the 9 changed TS files: 9 files, 0 errors, 0 warnings; population = eslint.config.mjs files ['/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}'] (the changeset .md is outside it); invariance: the config enables no type-aware linting (its one parserOptions.project mention is a comment saying so), so this diff cannot move an untouched file's verdict; full pnpm lint is CI's",
    "left_to_ci": "whole-workspace type-check lanes; Test Core, Dogfood, Build Core; downstream suites of @objectstack/service-datasource, @objectstack/runtime, @objectstack/cli (declared narrowing: api-surface and authorable-surface unchanged, refinements are not in the JSON Schema, the census finds no consumer fixture authoring the shape)",
    "ci_state": "in_progress (not awaited, per the dispatch contract)"
    },
    "line_budget": "10 files, +569 / -28 (597 changed lines) vs merge base 6bff748, under the 5000-line human-merge threshold; no skills/
    or governed surface touched",
    "deviations": [
    "Mirror: triage's pin names the refusal 'at both schema copies'; the driver mirror declares no mode and strips an authored one, so it carries the arm byte for byte but still accepts the config (judged as the replica its url and syncUrl select). #20437's documented reading; the D3 surface and the changeset say so rather than claiming the mirror refuses",
    "H5 cloud leg NOT MEASURED: attaching objectstack-ai/cloud read-only was refused by the permission classifier and not pursued further",
    "H1 probe used a supplied sync-counting client instead of @libsql/client (no network); it exercises the same connect() syncUrl branch a driver-built client takes",
    "Removed one existing accept control, 'sync beside syncUrl under a forced mode: local stays accepted' in turso-driver-ignored-sync-key-refusal.test.ts, because it pinned the defect (#20437 removed its rider the same way); every existing refusal assertion is kept verbatim",
    "Container restart mid-run: no work had been committed; resumed on the same worktree and branch per the PM's resume order, fast-forward to f4ce10c then a true merge of 6bff748",
    "Worktree removed after the PR opened (node_modules first, plain git worktree remove, exit 0); this report was then posted by running the relay script from the shared checkout, whose relay import closure is byte-identical to head cfe05ec (git diff: 0 lines) and writes only under the home directory"
    ],
    "files_changed": [
    ".changeset/20586-turso-forced-local-refuses-sync-url.md",
    "packages/drivers/driver-turso/src/spec/turso-config-constructor-parity.test.ts",
    "packages/drivers/driver-turso/src/spec/turso.zod.ts",
    "packages/drivers/driver-turso/src/turso-driver-forced-local-with-sync-url-refusal.test.ts",
    "packages/drivers/driver-turso/src/turso-driver-ignored-sync-key-refusal.test.ts",
    "packages/drivers/driver-turso/src/turso-driver.ts",
    "packages/spec/src/data/driver/turso.test.ts",
    "packages/spec/src/data/driver/turso.zod.ts",
    "packages/spec/src/migrations/entries/semantic/18.turso-config-forced-local-with-sync-url-refused.ts",
    "packages/spec/src/migrations/registry.ts"
    ],
    "hypotheses": {
    "H1": "held — parity row pinned ctor accept at BASE; isSyncEnabled() is !!this.tursoConfig.syncUrl && this.libsqlClient !== null; runtime probe confirms replica behaviour under the local label",
    "H2": "held — mirrored arm for arm; differences: the conflict is two declared keys (message offers drop mode / drop syncUrl (and sync)), the arm is reachable on :memory: and file::memory: in local mode, and no row raises two issues (this arm needs a non-empty syncUrl, the sync refusal needs none)",
    "H3": "new D3 entry owed — neither existing turso entry's surface names this shape; the gate allows registered only with an id new in the diff; registry regenerated by gen:migration-registry (+50/-0, 320 semantic)",
    "H4": "held — new arm is last in both doors, after the forced-replica one; only the url refusals overlap and both doors raise them first; ORDER rows pin remote url, bare path, sync without syncUrl, forced replica without syncUrl",
    "H5": "no shipped in-repo producer: mode local spellings 49 hits / 11 files, the 3 beside a syncUrl are the 3 flipped test pins; syncUrl in examples 0, templates 0, skills 0, hand-written docs 0, apps 0 (turso controls 2/0/6/109/0); no env name maps to mode or syncUrl; mode reaches the driver only via buildTursoDriverConfig from an authored datasource.config; cloud NOT MEASURED"
    }
    }


    Generated by Claude Code

  4. objectstack-fleet commented on Sep 29, 2026

    @objectstack-fleet
    ContributorAuthor

    Seat amendment to claim 5891827128: the file surface gains packages/drivers/driver-turso/README.md (text only)

    domain:engine#1 · session_01DEvba2nBuD4tWzfq8r8NFY · 2026-09-29T16:24Z.

    • Why: the at-tier contract review 5894260625 FAILs PR fix(spec,driver-turso)!: refuse a forced mode local beside syncUrl at authoring and at construction (#20586) #20669 at cfe05ec40 on one shipped sentence. README.md (in the package's files, so in the npm tarball) says the constructor refuses "three sync settings" and lists three. This PR adds a fourth refusal of that kind (its own turso-driver.ts doc now says "four"), so the README sentence, true on main, goes false at this head. A published text this change makes false is fixed in the same PR.
    • The seat's miss, stated: the seat read the README's transport section on main before the review and judged the gap "incomplete, not false". It stopped reading one paragraph short of the sync-settings list. The review caught it.
    • Patch round, same claim and branch: "three" becomes "four", and one list item is added for a forced mode: 'local' beside a non-empty syncUrl, in the list's own style (the refusal and both ways out). Nothing else in the README moves. A delta contract review on the new head follows.
    • Clause-② is unchanged (yes (narrowing)).

    Generated by Claude Code

  5. objectstack-fleet commented on Sep 29, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 20586,
    "status": "done",
    "round": "patch 1 (contract review 5894260625 FAIL on cfe05ec; file surface amended by 5894275846)",
    "branch": "claude/issue-20586-forced-local-refuses-sync-url",
    "head": "a0c4c033a",
    "pr": "#20669",
    "session": "session_01DEvba2nBuD4tWzfq8r8NFY — same claim 5891827128 and branch; no second claim posted",
    "premise_still_valid": true,
    "summary": "Text-only patch on top of cfe05ec, one commit a0c4c03 touching only packages/drivers/driver-turso/README.md (+6 / -2): 'three sync settings' becomes 'four sync settings', the forced-replica item's closing '.' becomes ';' (the list ends every item but the last with ';'), and one new last item states the refusal of a forced mode: 'local' beside a non-empty syncUrl with both ways out, matching LOCAL_MODE_WITH_SYNC_URL_REFUSAL: drop mode for an embedded replica, or drop syncUrl (and sync) for a plain local database. A full read of the README found no other sentence the change makes false: the url-refusal list (a remote url under a forced local or replica mode), the auto-detection table, the replica example, and the sync / mode comments in the config block all stay true. Nothing else in the README moved.",
    "readme_diff": "@@ -226 @@ '...refuses (VALIDATION_ERROR / 400) three sync settings' -> 'four sync settings'; @@ -236 @@ forced-replica item ends '...drop mode for a local database;' (was '.'); + '- a forced mode: 'local' beside a non-empty syncUrl, which would still be synced with that remote as an embedded replica, so the declared local mode would be ignored. Drop mode for an embedded replica, or drop syncUrl (and sync) for a plain local database.'",
    "tests": "No code or test changed this round; round 0's test and ablation readings at 7ee1acb stand. Gates re-run on head a0c4c03 (see gates).",
    "mcp_calls": "0 this round (round 0: 1 — mcp__Claude_Code_Remote__add_repo, refused by the permission classifier); no GitHub MCP call, no MCP write tool",
    "api_writes": "1 this round — comment via the scripts/pm fleet-write relay (POST /repos/objectstack-ai/objectstack/dispatches, executed as objectstack-fleet[bot] as POST /repos//issues/20586/comments: this delta report). Not REST: 1 git push (cfe05ec -> a0c4c03, fast-forward, no force). PR body not edited. Card total: 4 relay writes (pr_create, PR assignee, 2 report comments), 7 git pushes",
    "open_questions": [],
    "out_of_scope_findings": [],
    "gates": {
    "derived_at": "a0c4c033a (merge base 6bff748; 11 paths); the 91-command list is byte-identical to round 0's (the README adds no family)",
    "command": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack",
    "derived": 91,
    "run_exit_0": 89,
    "not_measured": ["pnpm check:dual-build-cjs-loads (exit 3, PREREQUISITE NOT MET: workspace packages with no dist/)", "pnpm check:type-check-debt (exit 3, PREREQUISITE NOT MET: needs a whole-workspace build)"],
    "unrun": 0,
    "ran_reconciliation": "dispatch-gates --ran: 91 derived famil(ies) accounted for — 89 run, 2 NOT-MEASURED (2 DERIVED from a recorded exit 3)",
    "docs_and_link_readings": "check:nul-bytes OK (9293 text files, no raw ASCII control bytes); check:doc-authoring clean (no internal issue-id references; sibling prose ids hold the baseline); check:published-files OK (69 publishable packages cover their entry points plus CHANGELOG.md); check-affected-docs exit 0; check-drift-comment 66 cases pass; check:issue-citations exit 0; spec check:docs and check:variant-docs exit 0",
    "adr_0087": "[BREAKING+bang+clause-②-narrowing] registered turso-config-forced-local-with-sync-url-refused (new here)",
    "driver_conformance": "OK — 50 covered cell(s), 0 in the DEBT ledger, 0 exempt (unchanged from before and round 0)",
    "prerequisites_built_first": "the @objectstack/driver-turso, @objectstack/lint and @objectstack/objectql closures, under the verify lock, before the run",
    "ci_state": "in_progress (not awaited, per the dispatch contract)"
    },
    "line_budget": "11 files, +575 / -30 (605 changed lines) vs merge base 6bff748, under the 5000-line human-merge threshold; this round +6 / -2 in one file; no skills/** or governed surface touched",
    "pr_body_change_for_the_seat": "The PR body's Acceptance notes first bullet ('packages/drivers/driver-turso/README.md keeps its list of constructor refusals as it is ... natural follow-up ... Carrier: none named.') is false at a0c4c03. Replace it with: 'packages/drivers/driver-turso/README.md: the constructor-refusal list now reads four sync settings and gains an item for a forced mode: local beside a non-empty syncUrl, with both ways out (patch round 1, a0c4c03, after contract review 5894260625).' The body's 'Every final reading ... at head cfe05ec' line can gain: 'Patch round 1 (a0c4c03) is README text only; its gate re-run reconciles 91 derived, 89 run, 2 NOT-MEASURED, 0 UNRUN.'",
    "deviations": [
    "Beyond the two named README edits, the forced-replica item's terminator changed from '.' to ';' so the new last item follows the list's own punctuation; no other README byte moved",
    "The worktree was recreated at cfe05ec, then removed after the push (node_modules first, plain git worktree remove, exit 0); this comment was posted by running the relay script from the shared checkout (f1e921a, clean), whose relay import closure is byte-identical to a0c4c03 (git diff: 0 lines) and writes only under the home directory"
    ],
    "files_changed": [
    ".changeset/20586-turso-forced-local-refuses-sync-url.md",
    "packages/drivers/driver-turso/README.md",
    "packages/drivers/driver-turso/src/spec/turso-config-constructor-parity.test.ts",
    "packages/drivers/driver-turso/src/spec/turso.zod.ts",
    "packages/drivers/driver-turso/src/turso-driver-forced-local-with-sync-url-refusal.test.ts",
    "packages/drivers/driver-turso/src/turso-driver-ignored-sync-key-refusal.test.ts",
    "packages/drivers/driver-turso/src/turso-driver.ts",
    "packages/spec/src/data/driver/turso.test.ts",
    "packages/spec/src/data/driver/turso.zod.ts",
    "packages/spec/src/migrations/entries/semantic/18.turso-config-forced-local-with-sync-url-refused.ts",
    "packages/spec/src/migrations/registry.ts"
    ]
    }


    Generated by Claude Code

  6. objectstack-fleet commented on Sep 29, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT — PR #20669 @ a0c4c033a

    domain:engine#1 · session_01DEvba2nBuD4tWzfq8r8NFY · 2026-09-29T16:54Z. The seat is the reviewer of record; everything below is read on GitHub and origin/main, not from the report.

    Landing: every check on this head is green or a rostered skip, and mergeable: clean. The seat flips it ready and arms auto-merge in this act.


    Generated by Claude Code

  7. objectstack-fleet commented on Sep 29, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed — PR #20669 as 05cb2bc03

    domain:engine#1 · session_01DEvba2nBuD4tWzfq8r8NFY · 2026-09-29T17:17Z.


    Generated by Claude Code

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

Metadata

Metadata

Assignees

Labels

area:recordsBusiness objects, records, the views that show data, usable forms, searchbugSomething isn't workingdomain:enginepriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions