Skip to content

fix(driver-turso,driver-sql): refusals and log lines state each decision in words instead of a tracker number (stage 4) - #20877

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-20513-stage4-driver-turso
Sep 30, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-20513-stage4-driver-turso

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Part of #20513
Clause-②: no

Stage 4 of 5 of this lane (driver-turso), under the maintainer's A / A ruling on the card, plus the three driver-sql aggregate refusals stage 1 held for their Turso twins. The card stays open for stage 5, so this PR carries no closing keyword. Text only: no error code, field name, HTTP status, export or control flow moves. Every changed source line sits inside a string literal: a TypeScript-scanner skeleton of each of the four source files (every literal replaced by one placeholder, pure-literal + joins collapsed, comments dropped) is identical at the merge base and at the head, and the same check reports DIFF on a file with a real code change (control).

What this does

The Turso driver's refusals and log lines sent the reader to a tracker number for the reason behind them. Most of the remote transport's numbers were bare ids from the repository that file used to live in (remote-transport.ts was moved here from the cloud repository), so in this repository they resolve to unrelated cards: a Vercel deploy fix, a CLI command, a version-packages PR. Each rewritten string now says the reason in words (form D, as the migration-entry rewrite and stages 1 to 3 applied it). Where the sentence already stated what was decided, only the citation goes. Where it did not, the decision is added in words:

Where Cited The sentence now says
remote-transport.ts non-boolean $null diagnostic objectstack 5347, objectstack 5368, cloud 1116 Every driver now refuses it, so one filter no longer gets a different answer per backend.
turso-driver.ts remote auto_number refusal (thrown, 501) 6944 It is refused rather than resolved, because resolving would write NULL into the slot and persist the row without its record number. This replaces "Until this change the same call RESOLVED and wrote NULL", which described history rather than today.
remote-canonical-backfill.ts info line 5770 The converted rows now hold the one canonical text form, and the column drops the unindexable read-side repair only once a pass finds nothing left to convert.
remote-canonical-backfill.ts unresolvable-remainder warning cloud 1005 (with a Chinese tag) A value that cannot be read as an instant is counted and reported, never guessed at.
everything else: the five aggregate refusals, the withheld cross-field and unbindable-comparand wire sentences, the full diagnostics of the unsupported-operator, unlowered-$between, undeclared and non-list combinator, non-node operand, non-object where, empty operator map, undefined comparand, unbindable comparand and non-boolean $exists refusals, and the unnumbered-upsert warning 5907, 10413, 6409, 6212, 5347, 5369, 5903, 5769, 5348, 6050, 7929, 8197, 7099, cloud 1003, 1004, 1051, 1058, 1066, 1073, 1075, 1076 The sentence already said what was decided; only the citation goes.

Each new sentence was checked against today's code, not only against the cited card: a non-boolean $null is refused by all five drivers (driver-sql's nonBooleanNullComparandError, inherited by driver-sqlite-wasm and Turso local; driver-memory's filter-refusal.ts door and query path; driver-mongodb's mongodb-filter.ts; this transport); refuseUngeneratableRemoteAutonumber runs on create, bulkCreate and a match-key-less upsert, and RemoteTransport.create builds its own INSERT without fillAutoNumberFields; the backfill marks a column canonical only when its post-pass probe finds nothing left to change, and leaves out-of-band digit rows alone, counted into unresolvedEpochTextRows.

The three held pairs, moved together

Stage 1 held three driver-sql aggregate refusals (undeclared function, count_distinct without field, per-aggregation filter) because driver-turso compares them byte for byte with the remote transport's copies. Both faces drop the citation in one commit (fe71ac88e), and the pins stay byte-exact: remote-transport-aggregate-function-refusal.test.ts (the undeclared median / array_agg / string_agg and miscased cases, the field-less count_distinct case, the per-aggregation filter case) and remote-transport-undeclared-aggregate-keys.test.ts (the func: spelling) compare the two runtime messages. The same commit drops the remote declared-but-uncompiled and date-bucket citations, whose driver-sql copies stage 1 already cleared, so those pairs read identically again.

One-off ablation on the committed head, run under a shell trap: the three old citations planted back on the driver-sql face only (scripts/ablation-replace.mjs, each anchor hit once, disk-verified), driver-sql rebuilt, each marker confirmed in dist/ (scripts/ablation-dist-preflight.mjs). The two driver-turso files then gave 10 failed, 23 passed: every pair has a live pin. After the restore (blob equal to HEAD, git diff HEAD empty), the rebuild, and all three markers confirmed absent from dist/, they gave 33/33. git status --porcelain was empty afterwards.

remote-transport-compile-refusal-seam.test.ts derives the remote $between arity sentence from driver-sql's by stripping a parenthesised id. Neither side carries one any more, so that strip is now an identity; no sentence in this stage is derived that way.

Order inside the stage

32 literals (47 occurrences) fit one PR, under the stop line, so the stage lands whole in four ordered commits. Each of the first three recomputes the ledger, so every commit is green on check:doc-authoring:

  1. fe71ac88e author-visible: the aggregate refusal family, 8 literals on both faces;
  2. ab16ca922 author-visible: the remote filter refusals and the auto_number refusal, 21 literals (36 occurrences), plus the two re-pinned tests;
  3. 8c7541655 log lines: 3 literals;
  4. 252a378f9 the changeset.

driver-turso has no ledgered src/-shipped test string (its two testkits carry no id).

Pins re-pinned: 2 tests in 2 test files (4 id assertions become 3 sentence assertions)

  • remote-transport-null-comparand-refusal.test.ts: "cites the ruling and the framework landing so both repos are findable" asserted three ids. It now states the ruling in words and asserts "It is refused rather than coerced" and "Every driver now refuses it, so one filter no longer gets a different answer per backend."
  • remote-transport-comparand-refusal.test.ts 234 matched two ids. It now asserts indistinguishable from "no rows matched".

A one-off mutation proves each moved pin can fail, run on the committed head with scripts/ablation-replace.mjs in wrap mode under a shell trap. The tests import the transport from src, so no build leg applies. "Every driver now" to "Every backend now": 1 failed / 54 passed. Appending "here" after "no rows matched": 1 failed / 26 passed. After each leg the blob equals HEAD and git diff HEAD is empty; both files then gave 82/82, and git status --porcelain had 0 lines.

No test outside driver-turso pins a changed string. Every fragment of every changed sentence was searched in all test files of the repository: the hits are unrelated uses of common phrases, and rest's rest-4xx-message-truncation.test.ts, whose own copy of a $null sentence is never compared with a driver's (see Acceptance notes).

Ledger burn-down

scripts/doc-authoring-prose-id.baseline.json was regenerated with node scripts/check-doc-authoring.mjs --census-ledger into a scratch file, so the growth refusal ran against the checked-in baseline, and then copied into place. Only these four rows move, and every one leaves:

File Occurrences before after (file, id) pairs before after
driver-turso/src/remote-transport.ts 40 0 (row leaves) 22 0
driver-turso/src/turso-driver.ts 2 0 (row leaves) 2 0
driver-turso/src/remote-canonical-backfill.ts 2 0 (row leaves) 2 0
driver-sql/src/sql-driver.ts (the three held) 3 0 (row leaves) 3 0
whole ledger 769 722 514 485

The ledger's file count goes from 204 to 200; the gate's printed pinned sites (id-bearing literals) go from 647 to 615. A census of every other package's (file, id) occurrences before and after is identical (722 = 722).

The census count, reconciled. The census counted 24 messages for driver-turso; the ledger holds 44 occurrences. The gate counts 29 string literals. The census folds a + chain into one message, and five two-literal chains (the $exists diagnostic, the undeclared-combinator tail, the $and / $or requirement, and the cross-field and unbindable wire sentences) make 29 literals into 24 messages. Those 29 literals carry 44 occurrences, the census's own "id occurrences 44". With driver-sql's 3 held literals, this stage moves 32 literals and 47 occurrences.

Verification (head 252a378f99)

  • Build: turbo run build over ./packages/* and ./packages/*/*, 71/71, under the verify lock. driver-sql was rebuilt directly by the ablation's restore leg, and driver-turso directly after the pin mutations (their restore rewrote remote-transport.ts). The new sentences are in dist/index.js of both packages; the only citations left in either dist/ are docblocks.
  • @objectstack/driver-turso tests (vitest run --maxWorkers=2, reading the rebuilt driver-sql dist/): 80 files passed; 2,195 tests passed, 33 skipped.
  • @objectstack/driver-sql tests: 201 files passed and 11 skipped (the live PG / MySQL cells; no server here); 3,254 tests passed, 188 skipped.
  • @objectstack/driver-sqlite-wasm (extends SqlDriver): 36 files, 675 tests passed.
  • Typecheck: @objectstack/driver-turso and @objectstack/driver-sql typecheck exit 0; tsc --listFiles for driver-turso reads 80 test files, both re-pinned ones among them.
  • Derived gates: node scripts/pm/dispatch-gates.mjs --commands (no paths; 8 paths against merge base 72f8c3820) names 71 commands, and all 71 exit 0. check:type-check-debt first answered exit 3 (PREREQUISITE NOT MET: driver-turso's type entry older than its sources, after the mutation restore); after the direct driver-turso rebuild it exits 0, and check:dts-closure, check:dual-build-cjs-loads and check:lean-entry-closure were re-run there too. --ran with exit codes recorded: "71 derived, 71 run, 0 NOT-MEASURED, 0 UNRUN". check:doc-authoring: "sibling-package prose ids hold the baseline — 615 pinned site(s) across 200 file(s) ... no growth, no burn-down unrecorded".
  • Narrowed lint: eslint --no-inline-config --format json over the 6 touched .ts files reports 6 files, 0 errors, 0 warnings. The resolved parserOptions are ecmaVersion: latest and sourceType: module only, with no project or projectService, so no type-aware rule can move an untouched file. Repo-wide pnpm lint is CI's.
  • NOT MEASURED locally (CI's): the live PG / MySQL cells of the driver-sql suite (the required Temporal Conformance (live PG + MySQL) job runs them), the Test Core shards, Dogfood, and the workspace type-check lanes. origin/main moved 4 commits after the branch point; none touches these files or the ledger, and CI tests the merge ref.

Acceptance notes

  • Cited cards read. The 15 objectstack issues cited read closed as completed, and PR 5368 is merged; no cited decision has been reversed. The 10 cloud-repository ids (1003, 1004, 1005, 1051, 1058, 1066, 1073, 1075, 1076, 1116) are not readable from this session (the REST read answers 403, repository not enabled). The same numbers in this repository are unrelated cards, so they were never resolvable here. Those sentences were rewritten from their own words and the file's docblocks, checked against today's code.
  • Noted, not filed (carrier: dead tracker citations in the domain:engine packages (645 sites, 160 numbers, 104 files): the ruling C+D stage for this lane (from #20556) #20595). remote-transport.ts's comments and docblocks still cite those bare cloud ids, which in this repository resolve to unrelated cards. Comments are outside this ruling and belong to dead tracker citations in the domain:engine packages (645 sites, 160 numbers, 104 files): the ruling C+D stage for this lane (from #20556) #20595.
  • Noted, not filed (carrier: stage 5 of this card). driver-memory's non-boolean $null refusal (filter-refusal.ts) still ends with its citation; driver-memory is in stage 5.
  • Noted, not filed (carrier: none). rest's rest-4xx-message-truncation.test.ts builds its own copy of driver-sql's $null sentence, citation included, as a truncation fixture. It is a test file outside the ledger, and nothing compares it with the driver's text.
  • Noted, not filed (carrier: none). The test titles in driver-turso's *.test.ts files still carry cloud ids (for example the refusal-envelope table in remote-transport-null-comparand-refusal.test.ts). They are test files, outside the ledger and outside the ruling's shipped-src/ scope.
  • Noted, not filed (carrier: none). The new $null sentence says "every driver", not "every evaluator": @objectstack/formula's matchesFilter reads a non-true $null as IS NOT NULL. Nothing here measured a public door that hands it a non-boolean, so this is a reading only.

Generated by Claude Code

…ided without a tracker number

The three aggregate refusals stage 1 held (undeclared function, count_distinct
without a field, per-aggregation filter) drop their citation on both faces in
one commit, so the byte-parity tests between driver-sql and the Turso remote
transport keep comparing one sentence. The remote transport's
declared-but-uncompiled and date-bucket refusals drop theirs too, which makes
them equal to driver-sql's copies again. Each sentence already stated the
decision, so only the citation goes. The prose-id ledger is recomputed with
--census-ledger: the driver-sql row leaves, and remote-transport.ts goes from
40 to 35 occurrences.

Claude-Session: https://claude.ai/code/session_01DEvba2nBuD4tWzfq8r8NFY
Co-authored-by: Claude <noreply@anthropic.com>
… say what was decided without a tracker number

The remote transport's filter refusals (the withheld wire sentences and the
full diagnostics behind them) drop their citations. Most of those numbers were
bare ids from the repository this file used to live in, so here they resolved
to unrelated cards. Where the sentence already stated the decision, only the
citation goes. Two gain words: the non-boolean $null diagnostic now says every
driver refuses it, so one filter no longer gets a different answer per backend,
and the remote auto_number refusal says it refuses rather than resolves because
resolving would write NULL into the slot.

Two pins move onto the sentence: the $null test asserts the ruling in words
instead of three ids, and the unbindable-comparand test asserts
'indistinguishable from "no rows matched".' instead of two ids. The prose-id
ledger is recomputed: the remote-transport.ts row leaves, and turso-driver.ts
keeps one occurrence (the log line).

Claude-Session: https://claude.ai/code/session_01DEvba2nBuD4tWzfq8r8NFY
Co-authored-by: Claude <noreply@anthropic.com>
… tracker number

The unnumbered-upsert warning drops its citation; it already said what an
operator should do. The remote canonical backfill's info line now says what a
conversion buys (the converted rows hold the one canonical text form, and the
column drops the unindexable read-side repair only once a pass finds nothing
left to convert). The unresolvable-remainder warning replaces its cross-repo
citation with the rule: a value that cannot be read as an instant is counted
and reported, never guessed at. The prose-id ledger is recomputed: the last two
driver-turso rows leave, so the package holds no ledgered id.

Claude-Session: https://claude.ai/code/session_01DEvba2nBuD4tWzfq8r8NFY
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling labels Sep 30, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

19 anchor(s) derived from 2 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run.

What this run could not see
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 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.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 14 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 6c96b37be84c496bf3e0dd4b89cd17999a0f4f62 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from d79a374a1b3dd100f13f77a912c06fcf4082b410 — the merge of head 252a378f9908e75d931168a222b3c868e74ea2e4 into base 6c96b37be84c496bf3e0dd4b89cd17999a0f4f62, 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 d79a374a1b3dd100f13f77a912c06fcf4082b410 && git checkout d79a374a1b3dd100f13f77a912c06fcf4082b410
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 6c96b37be84c496bf3e0dd4b89cd17999a0f4f62 252a378f9908e75d931168a222b3c868e74ea2e4 && git checkout -B drift-repro 6c96b37be84c496bf3e0dd4b89cd17999a0f4f62 && git merge --no-ff 252a378f9908e75d931168a222b3c868e74ea2e4

node scripts/docs-audit/affected-docs.mjs --json 6c96b37be84c496bf3e0dd4b89cd17999a0f4f62

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

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 252a378f9908e75d931168a222b3c868e74ea2e4
Local-runs: none

Read-only, at tier, adversarial to the dispatch. Inputs: card #20513 (body and all 25 comments — the census 5900801368, the ruling 5902360492 「20513 A」 A / A, the stage-4 claim 5912562820, the stage-4 os-dev-report 5913433803), PR #20877 (body, 8-file list, the net diff origin/main...252a378f99 at merge base 72f8c3820), PR #20795 at df67985b0 with its record 5906164285 (which held the three driver-sql aggregate refusals for their twins) and PR #20848 at a94f3bad3 with its record 5910833957 as the prior applications of the same ruling, the head's check-runs, the 16 cited objectstack cards, and the head's own source through git show / git grep / git diff. Nothing built, run or re-run; no worktree, no checkout. The PR head was confirmed as 252a378f9908e75d931168a222b3c868e74ea2e4 (branch claude/issue-20513-stage4-driver-turso, draft, Part of #20513, no closing keyword) before the read and had not moved.

Check-runs on 252a378f99, read once for this record at 2026-09-30T14:45Z and not polled: 32 check-runs — 24 completed / success (Build Core, Check Changeset, Check PR Size, Check Documentation Links, Flag docs affected by code changes, Governed Surface Queue Guard, Part-of PR must not also close its card, The card this PR closes must claim this branch, the two claim / single-writer guards, Dogfood Regression Gate and its three shards, Dogfood Verify CLI, Temporal Conformance (live PG + MySQL), Test Core 1/6, 2/6 and 4/6, Type Check · source gates, · debt ledger and · consumer gates, Auto Label, filter), 3 skipped (Build Docs, Console Pin Gate, Packed-tarball smoke — path-filtered), 5 in_progress (Lint & Repo Gates, Test Core 3/6, 5/6 and 6/6, Type Check · workspace), 0 failure. Their conclusions are the gate verdicts: the runs still going carry check:doc-authoring (Lint & Repo Gates) and part of the driver suites (Test Core). Temporal Conformance, which runs the whole driver-sql suite against live PG and MySQL, is success. This record judges the contract on the diff, the cards and the head's source; a failure among the five open runs, if one lands, is a new fact for the adopting session, not one this record has read.

① Derived judgments

Scope against the ruling and the claim — right. The ruling orders stages per package until the ledger is empty, all three categories, form D, --census-ledger in the same PR, author-visible text first and log lines last; the claim scopes stage 4 to packages/drivers/driver-turso/src/**, the three held driver-sql aggregate rows, the pins, the ledger and one patch changeset for both packages, and forbids wire code / field / status changes, comment or docblock edits (#20595) and any other package's ledger rows. The 8-file list is exactly that surface: 4 source files (3 in driver-turso, sql-driver.ts), 2 test files, the ledger, the changeset. No governed path. The order inside the stage is honoured as commit order (fe71ac88e the aggregate family on both faces, ab16ca922 the filter refusals and the auto_number refusal, 8c754165 the three log lines, 252a378f9 the changeset); 32 literals sit under the stop line, so nothing was left to split.

Text only — right, checked line by line. Every removed and added line in the four source files carries a string-literal delimiter; a filter of the diff for changed lines without one returns nothing. No err.code, err.status, StandardErrorCode member, exported symbol, field name, branch or call moves: every throw, return this.withheldRefusal(, logger?.info?.( / logger?.warn( call and its second argument is a context line. auto_number still maps to TEXT (remote-transport.ts about 2695). Comments and docblocks are untouched — the diff contains no comment line, and the bare cloud ids in remote-transport.ts's docblocks are still there on the head (③). The dev's scanner-skeleton claim is consistent with this reading; what this record verified is the diff itself.

Accept-set and public surface — nothing moves — right. No schema, filter grammar, DDL, route, wire code, HTTP status, field name, default or export changes; a caller sees different prose in the same envelopes (INVALID_QUERY / 400, NOT_IMPLEMENTED / 501, INVALID_FILTER / 400). No added line carries a tracker id (0 hits over the diff's + lines).

Ledger diff — right, exact, recounted. Parsing the baseline at origin/main and at the head: files 204 to 200, (file, id) pairs 514 to 485, occurrences 769 to 722. Exactly four rows leave and no row is added or changed: remote-transport.ts 22 pairs / 40 occurrences, turso-driver.ts 2 / 2, remote-canonical-backfill.ts 2 / 2, sql-driver.ts 3 / 3 (#5907, #10413, #6409 — the three stage 1 held). Reconciled against the source diff: the removed lines carrying an id are 25 in remote-transport.ts (40 id occurrences), 2 (2) in turso-driver.ts, 2 (2) in the backfill, 3 (3) in sql-driver.ts — 47, matching the PR body's 32 literals / 47 occurrences once the five two-literal + chains are counted as the dev counts them. A git grep of the head over the four files finds no tracker id on any non-comment line holding a string delimiter. check:doc-authoring inside Lint & Repo Gates is the mechanical confirmation, pending at read time.

The three held pairs — right, both faces in one commit, and they read identically. At the head the function bodies of undeclaredAggregateFunctionError and refuseDistinctAggregateWithoutField are byte-identical between sql-driver.ts and remote-transport.ts (a diff of the two extracted bodies is empty for each). The per-aggregation filter pair differs only by the backend-name interpolation (${backend} against the literal Turso remote transport) and return against throw — both pre-existing, the message text otherwise identical. The two pairs stage 1 left differing by the citation only now differ by nothing in their text: the declared-but-uncompiled pair by the list constant's name only (SQL_AGGREGATE_FUNCTIONS / REMOTE_AGGREGATE_FUNCTIONS, which the parity test says render the same six functions today), the date-bucket pair by the Bucketed here: interpolation that each face fills for itself, whose pinned contract is first-sentence parity (remote-transport-groupby-node.test.ts about 460-463, "the tails differ on purpose"). The pins compare runtime messages from the two packages, not two copies of a literal: remote-transport-aggregate-function-refusal.test.ts about 348 (expect(remote.message).toBe(local.message) over median / array_agg / string_agg) and remote-transport-undeclared-aggregate-keys.test.ts about 201. The dev's ablation (10 of 33 red with the driver-sql face alone re-cited) is its own measurement; the byte identity on the head is what this record verified. sql-driver.ts's row leaves the ledger, so stage 1's hold is discharged.

Form D, string by string — right. All 15 cited objectstack issues read closed / completed and PR #5368 is merged; no cited decision has been reversed. The 10 cloud-repository ids (1003, 1004, 1005, 1051, 1058, 1066, 1073, 1075, 1076, 1116) are not readable from this repository and resolve here to unrelated cards, as the dev says; those sentences were judged against the head's code and the file's own docblocks. The four literals that gained words, each judged against the card and the head's code:

  • Non-boolean $null diagnostic (5347, 5368, cloud 1116): "Every driver now refuses it, so one filter no longer gets a different answer per backend." True on the head for all five drivers: driver-sql throws nonBooleanNullComparandError on an own, non-boolean $null (sql-driver.ts about 4972-4977), inherited by driver-sqlite-wasm (extends SqlDriver) and by Turso local; driver-memory's single door assertFilterConditionShape refuses it (filter-refusal.ts about 937) and is called on the matcher (memory-matcher.ts about 55), the query path (memory-driver.ts about 1361) and the analytics face (memory-analytics.ts about 1592); driver-mongodb throws on its walk (mongodb-filter.ts about 303-306); the remote transport in this very method. The historical clauses the sentence keeps ("compiled IS NULL … compiled IS NOT NULL … dropped the constraint entirely") are past tense and are what $null 的比较值不是布尔时,driver-sql 与 driver-memory 给出**完全相反**的答案(一个 IS NULL,一个 IS NOT NULL)—— 实测 #5347 measured. "Driver" rather than "evaluator" is exact: @objectstack/formula's matchesFilter answers a non-true $null as not-null (matches-filter.ts about 719) — ③ item for reach. No pin compares this diagnostic byte for byte with the local face (turso-local-remote-null-parity.test.ts uses toContain on the shared requirement sentence; the seam test's parity is over the short withheld sentence, which is unchanged), so words added on the remote face alone break nothing. $null 的比较值不是布尔时,driver-sql 与 driver-memory 给出**完全相反**的答案(一个 IS NULL,一个 IS NOT NULL)—— 实测 #5347's title is the opposite-answers finding, fix(driver-sql,driver-memory,driver-mongodb): 越界过滤输入在门口拒收,而不是每个后端各答一个 (#5347, #5348) #5368 the landing that refuses at the door.
  • Remote auto_number refusal (6944): "It is refused rather than resolved, because resolving would write NULL into the slot and persist the row without its record number." refuseRemoteAutonumber is thrown by refuseUngeneratableRemoteAutonumber on create (turso-driver.ts about 2376), on bulkCreate (about 3048) and on an upsert that cannot merge (about 2431); RemoteTransport.create builds its own INSERT INTO … VALUES (remote-transport.ts about 1768) and the transport never references fillAutoNumberFields; the slot is a TEXT column. That the un-refused leg persists the row with a NULL number is exactly what Turso remote:带 id/conflictKeys 但没匹配上的 upsert 仍会静默写入 NULL 自增号(#6944 拒绝闸门覆盖不到的那条腿) #7099 records, and the unnumbered-upsert warning three lines down (about 2358: "the row is persisted, its record number is not") says the same for the leg the refusal cannot classify. TursoDriver remote 面根本不生成自增号:RemoteTransport.create 自建 INSERT,auto_number 只是个 TEXT 列 #6944's title is this gap ("remote 面根本不生成自增号 … 自建 INSERT"). The replaced clause ("Until this change the same call RESOLVED and wrote NULL") described history; the new one is present tense and true.
  • Backfill info line (5770): "the converted rows now hold the one canonical text form, and the column drops the unindexable read-side repair only once a pass finds nothing left to convert." The converge and epoch UPDATEs set the column to the canonical expression; the mark is base.canonical = after.residual === 0 && after.epochInBand === 0 on a post-pass probe, under the comment "The ONLY thing that earns the mark: nothing is left for either phase to change", and the module header says "Marked only when proved". The info line prints with canonical: report.canonical in its payload, so a pass that converted rows without earning the mark says so. drivers(turso): remote 模式补 canonical backfill 通道(@libsql/client,分批、可恢复、完成标记)—— cloud#1005 移交,维护者已裁方案 1 #5770 is the remote backfill channel ("分批、可恢复、完成标记"). True.
  • Unresolvable-remainder warning (cloud 1005): "a value that cannot be read as an instant is counted and reported, never guessed at." unresolvedEpochTextRows = max(0, after.epochText − after.epochInBand); the epoch phase's WHERE selects the in-band rows only, so out-of-band digit rows are not written; the count reaches the operator through this warn and the report. The module docblock (about 81) records the same rule in the file's own words ("rather than guessed at"). True.

Where only the citation goes (28 literals): each sentence already carried the decision and is otherwise unchanged, and each readable card's title matches its sentence — the undeclared-function (5907), declared-but-uncompiled (5907), field-less count_distinct (6409), per-aggregation filter (10413) and date-bucket (6212) refusals on the remote face and the three driver-sql twins; the non-boolean $exists diagnostic (5347, 5369, 5903); the unlowered $between (cloud 1003), unsupported operator (cloud 1004), undeclared combinator both arms (5769, 5348), non-list combinator (5769), undefined comparand (6050 — the "Undefined binding(s)" / IS NULL split is its title), non-node operand both arms (cloud 1051, 1076 / 1004, 1058, 1066, 1073), non-object where (cloud 1004, 1058, 1066, 1073, 1075), empty operator map (cloud 1004, 1058, 1066), the withheld cross-field sentence (cloud 1058, 7929) and the unbindable-comparand diagnostic and wire sentence (cloud 1004, 1058, 8197); the unnumbered-upsert warning (7099). The withheld-detail sentences ("withheld from the message; the full diagnostic is in the server log") now read as driver-sql's do after stage 1.

Pins — right, 2 tests in 2 files. remote-transport-null-comparand-refusal.test.ts about 177-183 now asserts "It is refused rather than coerced" and the full "Every driver now refuses it, so one filter no longer gets a different answer per backend." — both present in the head's runtime string (the second spans a + boundary in the source and is one sentence at runtime); the title moved with the assertions, which is right, since the old title described asserting ids. remote-transport-comparand-refusal.test.ts 234 asserts indistinguishable from "no rows matched". on the unbindable-comparand refusal, whose line 233 already ties it to the comparand-type sentence; the phrase is on the head at about 4714 (and, with the same period, in the unsupported-operator sentence — harmless, since the test's own find produces the comparand refusal). The dev's mutation legs (1 / 54, 1 / 26) are its own measurement. No test outside driver-turso pins a changed string; the one lookalike is rest's fixture (③).

Review faces — right, with one wording note. The changeset (below) reads as a CHANGELOG entry: every family it names is in the diff, and each sentence it summarises matches the string. The PR body's table, counts, commit order and ledger numbers match the diff and the recount above; it carries Clause-②: no at line start and no closing keyword. The note: the changeset says the declared-but-uncompiled and date-bucket refusals "read exactly like the SQL driver's again" — exact for the sentence templates, while the rendered date-bucket line still names each face's own bucketed list (the declared asymmetry its pin protects). Not a false statement of behaviour; carrier: whoever next edits that changeset, if anyone does before release.

② Semver level

.changeset/20513-driver-turso-runtime-strings-state-the-decision.md: '@objectstack/driver-turso': patch and '@objectstack/driver-sql': patch — right. Both packages are released (17.5.0, not private); the diff publishes changed prose in those two packages and nothing else — no key, export, envelope code, status or default moves — so this is a fix-class patch in each, never skip-changeset. The body is CHANGELOG-fit: it names the message families, says which sentences gained words and what they now say, and closes "Text only: no error code, field name, status or behaviour changes". No model identifier in the changeset, the four commit trailers (Claude-Session plus a model-free Co-authored-by) or the PR body (session-URL footer) — the shape stages 1 to 3 landed with. Check Changeset is success.

Clause-②: no — right. Nothing an author can write is widened or narrowed; the PR body carries Clause-②: no at line start and the changeset repeats it.

③ Boundary flags

Dev flags (report 5913433803), each answered:

  1. Base 72f8c3820, main not merged before the PR — accepted. The merge base of origin/main and the head is 72f8c3820154c69cdf9faa6f887dc544e4c4b823; at read time origin/main (07356a6ab0) is 7 commits past it, and git log over the 8 changed paths between the two is empty — none touches these files or the ledger. CI tests the merge ref, and every completed run is green.
  2. Two pins re-pinned, one title changed with its assertions — verified in ①.
  3. Cloud ids in remote-transport.ts comments and docblocks (carrier dead tracker citations in the domain:engine packages (645 sites, 160 numbers, 104 files): the ruling C+D stage for this lane (from #20556) #20595) — accepted. Comments are outside the ruling and untouched in the diff; the head's docblocks still carry them, which is dead tracker citations in the domain:engine packages (645 sites, 160 numbers, 104 files): the ruling C+D stage for this lane (from #20556) #20595's surface.
  4. driver-memory's nonBooleanNullComparandError still cites #5347 (carrier: stage 5) — accepted, and verified. filter-refusal.ts about 617 ends "($null 的比较值不是布尔时,driver-sql 与 driver-memory 给出**完全相反**的答案(一个 IS NULL,一个 IS NOT NULL)—— 实测 #5347)."; its ledger row is another package's, which the claim forbids; the leading sentence is the verbatim twin and the two faces now differ by the citation only, exactly the situation stage 1 left the remote aggregate pairs in. Held, not lost.
  5. rest's truncation fixture (carrier: none) — accepted as stated. rest-4xx-message-truncation.test.ts 52-59 builds its own copy of driver-sql's $null sentence with "($null 的比较值不是布尔时,driver-sql 与 driver-memory 给出**完全相反**的答案(一个 IS NULL,一个 IS NOT NULL)—— 实测 #5347)."; a .test.ts file outside the ledger, and its assertions (about 104-105) check the operator / field / spec sentence, never the citation, and never compare with a driver's text.
  6. Test titles carrying cloud ids (carrier: none) — accepted. 31 describe / it titles in driver-turso's test files carry a four-digit cloud id; test files are outside the ledger and outside the ruling's shipped-src/ scope.
  7. The seam test's now-identity strip (carrier: none) — accepted. remote-transport-compile-refusal-seam.test.ts about 619 still derives the remote $between arity sentence as [RemoteTransport] plus the local one with / \(#\d+\)/g stripped; neither face carries a parenthesised id now, so the strip is an identity. What it would tolerate — a citation returning on the local face alone — is what check:doc-authoring's growth refusal exists for, so the tolerance is harmless. Carrier none is right.
  8. @objectstack/formula matchesFilter answering a non-true $null as not-null — a reading only, not a defect worth a card, and here is the reach that says so. The arm is case '$null': return v === true ? actual == null : actual != null (matches-filter.ts about 719), the same two-branch shape $null 的比较值不是布尔时,driver-sql 与 driver-memory 给出**完全相反**的答案(一个 IS NULL,一个 IS NOT NULL)—— 实测 #5347 measured on the drivers. Its callers on the head hand it filters no author writes raw: the RLS write-side check (security-plugin.ts about 3226, step 3.6) and the delegator check come from computeWriteCheckFilter, and a policy's check is a CEL string by schema (rls.zod.ts about 499) that compileCelToFilter lowers to $null: true / $null: false only (cel-to-filter.ts about 668 and 673); the tenant wall (about 3530) is computed by the plugin; the explain engine (about 964) reads the same compiled policies; objectql's HAVING (having-filter.ts about 1208) hands it a single resolved operator after its own admission walk. No public door hands the evaluator a non-boolean $null, and the evaluator's own header declares a total, answer-not-throw posture with three ruled exceptions ({ field: {} }(零个操作符的字段约束)在同仓有三个答案:driver-sql 组合子内 TRUE、顶层抛 INVALID_FILTER、formula/driver-memory FALSE #5240, [finding] $ne with an array comparand splits across backends: driver-sql and driver-memory refuse (400), driver-mongodb answers, formula matches every row — and both shared faces pass it #19886, RLS enforcement: the write check (packages/formula matches-filter) admits a cross-class field-to-field comparison that driver-sql's read refuses — one classification, one answer per policy (the engine half of #20347) #20355). Making $null a fourth would be a ruling of the { field: {} }(零个操作符的字段约束)在同仓有三个答案:driver-sql 组合子内 TRUE、顶层抛 INVALID_FILTER、formula/driver-memory FALSE #5240 kind, not a bug fix; the sentence's "every driver" is the exact claim. Carrier none, as the dev says — recorded here so a later reader has the measurement.
  9. Commit trailers (the model-free pair, not the harness reminder's line) — accepted, as stages 1 to 3 were under AGENTS.md's rule.
  10. NOT MEASURED locally (live PG / MySQL, Test Core shards, Dogfood, workspace type-check) — CI's; at read time Temporal Conformance, Dogfood and its three shards, Build Core, Test Core 1/6, 2/6 and 4/6 and three of the four type-check lanes are success, the rest in_progress, none failure.

open_questions: none in the stage-4 report; none raised here. The census reconciles as the dev states: its driver-turso row is 24 messages with "id occurrences 44", and the ledger at the base held 44 across 3 files; the 20 line-site entries in the census's per-file list fold the + chains the same way. The card stays open for stage 5 (driver-memory and the other packages, plus the engine.ts #4419 row stage 3 held); the lane children (#20749, #20751, #20753, Blocked-by: #20513) are unaffected; the ledger's other rows are untouched.

Implemented-by: claude/issue-20513-stage4-driver-turso
Reviewed-by: session_01DEvba2nBuD4tWzfq8r8NFY

VERDICT: PASS


Generated by Claude Code

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.

2 participants