Skip to content

fix(driver-turso): a remote upsert that lands a NULL record number now says so (#7099) - #8412

Merged
os-zhuang merged 2 commits into
mainfrom
claude/issue-7099-turso-upsert-null-autonumber-warning
Aug 13, 2026
Merged

fix(driver-turso): a remote upsert that lands a NULL record number now says so (#7099)#8412
os-zhuang merged 2 commits into
mainfrom
claude/issue-7099-turso-upsert-null-autonumber-warning

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes #7099

What changes — and what deliberately does not

#6944 made the Turso REMOTE face refuse an auto_number write it cannot fulfil instead of silently writing NULL. One leg was left uncovered and declared as known residue: an upsert carrying an id or explicit conflictKeys that matches nothing still inserts, and the record-number slot still lands NULL.

That outcome is unchanged here. What changes is that it is no longer silent.

upsert({ id: 'never-seen', … })  ->  RESOLVED case_number=null    (before: nothing said)
                                     RESOLVED case_number=null
                                     + logger.warn naming the object,
                                       the column and the row id   (now)

Out of scope, unchanged, per the triage promotion:

The premise, re-measured

The residue was recorded under the claim that classifying this leg "needs the round trip this refusal exists to avoid". That round trip is already paid, unconditionally: RemoteTransport.upsert follows its INSERT … ON CONFLICT with a SELECT by id and returns the mapped row — the residue pin's own expect(inserted.case_number).toBeNull() was reading that very result. The NULL was in hand all along, one field read away. No probe query was added, and none is needed.

Where the detection lives — the one place this PR departs from the suggested shape

The promoted scope suggested handing the autonumber rule down to RemoteTransport, following the setFilterColumnSql / setDiagnosticSink plumbing precedent. Measured, the driver is the better layer and needs no plumbing at all:

  1. TursoDriver.upsert already receives the transport's returned row, and already holds autoNumberFields[object] — both halves of the detection are in hand at the same frame.
  2. Its logger.warn is literally the sink setDiagnosticSink((message) => this.logger.warn(message)) forwards to, so routing through the transport would be a longer path to the same log line, with no added reach.
  3. An existing pin forbids the transport-side shape: turso-remote-autonumber-refusal.test.ts asserts no member of RemoteTransport.prototype matches /autonumber|sequence/i, encoding TursoDriver remote 面根本不生成自增号:RemoteTransport.create 自建 INSERT,auto_number 只是个 TEXT 列 #6944's layering decision ("RemoteTransport cannot see a field type, so it cannot be the one to refuse"). Teaching the transport this rule would have gone red there — correctly.

So: detection and reporting both on TursoDriver, zero new mechanisms, and the schema-blind transport stays schema-blind.

False positives were designed against, not hoped away

The check reads the result row, not the request — a merging upsert leaves the slot empty in the request too, so a request-shaped rule would warn on the one leg #6944 measured as correct. Three silence controls pin that: the MERGE leg, an object with no auto_number field, and a caller-supplied number on this same leg (the seed-replay / import path). The empty-slot predicate is now stated once and read by both the pre-write refusal and the post-write report, so "empty" cannot acquire two spellings.

Reported at warn, not error: everything the caller submitted persisted, and the returned row carries the null in plain sight; what is missing is a derived value this face declares it does not issue. Per AGENTS.md §Degradation log levels that is a functional degradation, not a durability one. Not throttled, unlike the tenant-audit warning it sits beside — here the row id is the payload.

Reverse verification — both directions predicted before running

prediction measured
① fix reverted, tests kept exactly 1 red: the pin, on the warning half, never the NULL half 1 failed / 19 passedexpected [] to have a length of 1
② predicate dropped (over-firing) exactly 2 red: MERGE-leg + caller-supplied controls; the pin itself green 2 failed / 18 passed — both expected [ Array(1) ] to deeply equal []

② is the half ① cannot prove: the pin alone cannot tell a correct warning from a wolf-crying one, and the controls are what do. Also green in ②: the no-auto_number-field control, because the registry lookup returns before the predicate is reached.

Verification

pnpm --filter @objectstack/driver-turso test — 35 files, 969 passed. typecheck clean, eslint clean on both changed files. Gates derived from the actual changed paths via scripts/pm/dispatch-gates.mjs and run green: check:driver-conformance, check:nul-bytes, check:changeset-gate-self-tests, check:objectui-changeset, check:test-source-alias, check:type-source-resolution, check:query-options-erasure, check:type-check-coverage, check:durability-log-level, check-changeset-no-major. Two reds not caused by this diff: check:objectui-pin-fresh (known-ambient, fires for any PR carrying a changeset) and check-dev-prereqs (my worktree only built the driver's dependency closure, not all 67 packages).

Changeset: patch — no public export moves, the new member is private, and no caller-visible behaviour changes; the only new output is an operator log line where there was silence.


Generated by Claude Code

claude added 2 commits August 13, 2026 11:45
… verifications

Adds the changeset and the test-file header section: why the detection lives on
TursoDriver rather than being plumbed into RemoteTransport, and the two
predicted-then-measured reverse verifications (fix reverted; predicate dropped).
@vercel

vercel Bot commented Aug 13, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 13, 2026 11:56am

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/driver-turso.

6 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/data-modeling/drivers.mdx (via @objectstack/driver-turso)
  • content/docs/deployment/cli.mdx (via @objectstack/driver-turso)
  • content/docs/deployment/environment-variables.mdx (via @objectstack/driver-turso)
  • content/docs/deployment/self-hosting.mdx (via @objectstack/driver-turso)
  • content/docs/getting-started/glossary.mdx (via @objectstack/driver-turso)
  • content/docs/plugins/packages.mdx (via @objectstack/driver-turso)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling labels Aug 13, 2026
@os-zhuang
os-zhuang marked this pull request as ready for review August 13, 2026 12:23
@os-zhuang
os-zhuang added this pull request to the merge queue Aug 13, 2026
Merged via the queue into main with commit a8d835b Aug 13, 2026
26 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-7099-turso-upsert-null-autonumber-warning branch August 13, 2026 12:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Turso remote:带 id/conflictKeys 但没匹配上的 upsert 仍会静默写入 NULL 自增号(#6944 拒绝闸门覆盖不到的那条腿)

2 participants