Skip to content

Commit 42d78b9

Browse files
fix(driver-turso,driver-sql): refusals and log lines state each decision in words instead of a tracker number (stage 4) (#20877)
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: #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 #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](https://claude.ai/code/session_01DEvba2nBuD4tWzfq8r8NFY)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent ca5408c commit 42d78b9

8 files changed

Lines changed: 77 additions & 78 deletions
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
---
2+
'@objectstack/driver-turso': patch
3+
'@objectstack/driver-sql': patch
4+
---
5+
6+
driver-turso refusals and log lines, and driver-sql's last three aggregate refusals, no longer cite tracker numbers; each states the reason in words
7+
8+
Clause-②: no
9+
10+
Many messages the Turso driver shows to authors and operators ended with an issue-tracker number where
11+
the reason belonged. Most of the Turso remote transport's numbers were bare ids from the repository that
12+
file used to live in, so here they pointed at unrelated cards. The number goes, and where the sentence
13+
did not already say what was decided, it now does:
14+
15+
- Aggregate refusals, on both drivers: the undeclared-function, `count_distinct`-without-`field` and
16+
per-aggregation `filter` refusals lose their citation on the SQL driver and the Turso remote
17+
transport together, so the two faces still read one sentence. The remote transport's
18+
declared-but-uncompiled and date-bucket refusals lose theirs too, and read exactly like the SQL
19+
driver's again.
20+
- Turso remote filter refusals (`INVALID_FILTER`): the withheld cross-field and unbindable-comparand
21+
wording, and the full diagnostics behind every filter refusal (unsupported operator, unlowered
22+
`$between`, undeclared or non-list combinator, non-node operand, non-object `where`, empty operator
23+
map, undefined comparand, non-boolean `$exists`) lose only the citation, because their sentences
24+
already said it. The non-boolean `$null` diagnostic now says every driver refuses it, so one filter
25+
no longer gets a different answer per backend.
26+
- The Turso remote `auto_number` refusal (`NOT_IMPLEMENTED`) now says why it refuses rather than
27+
resolves: resolving would write NULL into the slot and persist the row without its record number.
28+
- Log lines: the unnumbered-upsert warning loses its citation; the remote canonical backfill's info line
29+
says what a conversion buys (the column drops the unindexable read-side repair only once a pass finds
30+
nothing left to convert); the unresolvable-remainder warning says a value that cannot be read as an
31+
instant is counted and reported, never guessed at.
32+
33+
Text only: no error code, field name, status or behaviour changes.

‎packages/drivers/driver-sql/src/sql-driver.ts‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1696,7 +1696,7 @@ function undeclaredAggregateFunctionError(func: string): Error {
16961696
`Declared functions: ${DECLARED_AGGREGATE_FUNCTIONS.join(', ')} ` +
16971697
`(@objectstack/spec AggregationFunction). Fix the "function" key of the aggregations[] ` +
16981698
`entry — the Query Protocol has no such function, so this is a query no backend can run, ` +
1699-
`not a gap in this one (#5907).`,
1699+
`not a gap in this one.`,
17001700
) as Error & { code?: string; status?: number };
17011701
err.code = StandardErrorCode.enum.INVALID_QUERY;
17021702
err.status = 400;
@@ -1800,7 +1800,7 @@ function unsupportedAggregationFilterError(alias: string, backend: string): Erro
18001800
`Per-aggregation \`filter\` on "${alias}" is not supported by this backend (${backend}). ` +
18011801
`The query is spelled correctly and @objectstack/spec AggregationNodeSchema declares the key — ` +
18021802
`this backend compiles no conditional-aggregate (SQL FILTER (WHERE …) / CASE WHEN) expression ` +
1803-
`for it, so it is refused rather than silently aggregating the UNFILTERED rows (#10413), which ` +
1803+
`for it, so it is refused rather than silently aggregating the UNFILTERED rows, which ` +
18041804
`is why it answers NOT_IMPLEMENTED/501 rather than a 400. \`engine.aggregate\` lowers filtered ` +
18051805
`aggregations in memory for every driver without native support — route the query through the ` +
18061806
`engine, or drop the \`filter\` key.`,
@@ -1834,7 +1834,7 @@ function refuseDistinctAggregateWithoutField(func: string): never {
18341834
const err = new Error(
18351835
`Aggregate function "${func}" needs a "field" — there is nothing to deduplicate. ` +
18361836
`COUNT(*) counts rows and is the spelling that takes no field; a distinct count has to name ` +
1837-
`the column whose values are deduplicated. Add "field" to the aggregations[] entry (#6409).`,
1837+
`the column whose values are deduplicated. Add "field" to the aggregations[] entry.`,
18381838
) as Error & { code?: string; status?: number };
18391839
err.code = StandardErrorCode.enum.INVALID_QUERY;
18401840
err.status = 400;

‎packages/drivers/driver-turso/src/remote-canonical-backfill.ts‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -810,7 +810,9 @@ export async function backfillRemoteCanonicalColumns(
810810
);
811811
} else if (report.rowsConverted || report.epochTextRowsConverted) {
812812
logger?.info?.(
813-
`[driver-turso] canonicalised remote ${column.kind} storage (#5770) for ${where}`,
813+
`[driver-turso] canonicalised remote ${column.kind} storage for ${where}: the converted ` +
814+
`rows now hold the one canonical text form, and the column drops the unindexable ` +
815+
`read-side repair only once a pass finds nothing left to convert`,
814816
{
815817
rowsConverted: report.rowsConverted,
816818
epochTextRowsConverted: report.epochTextRowsConverted,
@@ -833,8 +835,8 @@ export async function backfillRemoteCanonicalColumns(
833835
if (report.unresolvedEpochTextRows > 0) {
834836
logger?.warn(
835837
`[driver-turso] ${report.unresolvedEpochTextRows} row(s) in ${where} hold digits-only ` +
836-
`text outside the interpretable epoch-millisecond band and were left untouched ` +
837-
`(cloud#1005 后果 B, unresolvable remainder)`,
838+
`text outside the interpretable epoch-millisecond band and were left untouched: a value ` +
839+
`that cannot be read as an instant is counted and reported, never guessed at`,
838840
{ table: column.table, field: column.field },
839841
);
840842
}

‎packages/drivers/driver-turso/src/remote-transport-comparand-refusal.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ describe('RemoteTransport comparand refusal — the value half of #1004', () =>
231231
const { t } = transportWithCapturingClient();
232232
const err = await t.find('deal', { where: { payload: { $eq: { a: 1 } } } }).catch((e) => e);
233233
expect(err.message).toMatch(/must be a string, number, bigint, boolean, null or Date/);
234-
expect(err.message).toMatch(/#1004, #1058/);
234+
expect(err.message).toContain('indistinguishable from "no rows matched".');
235235
});
236236

237237
it('truncates a large comparand rather than pasting it into the log', async () => {

‎packages/drivers/driver-turso/src/remote-transport-null-comparand-refusal.test.ts‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -174,11 +174,12 @@ describe('RemoteTransport $null comparand refusal (#1116)', () => {
174174
expect(err.message).toContain('the side opposite the false it was written to mean');
175175
});
176176

177-
it('cites the ruling and the framework landing so both repos are findable', async () => {
177+
it('states the ruling and its landing in words: refused, not coerced, by every driver', async () => {
178178
const err = await refusalOf({ stage: { $null: 'yes' } });
179-
expect(err.message).toContain('objectstack#5347');
180-
expect(err.message).toContain('objectstack#5368');
181-
expect(err.message).toContain('#1116');
179+
expect(err.message).toContain('It is refused rather than coerced');
180+
expect(err.message).toContain(
181+
'Every driver now refuses it, so one filter no longer gets a different answer per backend.',
182+
);
182183
});
183184

184185
it('names the spec declaration rather than only complaining', async () => {

‎packages/drivers/driver-turso/src/remote-transport.ts‎

Lines changed: 26 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -945,7 +945,7 @@ function undeclaredAggregateFunctionError(func: string): Error {
945945
`Declared functions: ${DECLARED_AGGREGATE_FUNCTIONS.join(', ')} ` +
946946
`(@objectstack/spec AggregationFunction). Fix the "function" key of the aggregations[] ` +
947947
`entry — the Query Protocol has no such function, so this is a query no backend can run, ` +
948-
`not a gap in this one (#5907).`,
948+
`not a gap in this one.`,
949949
) as Error & { code?: string; status?: number };
950950
err.code = StandardErrorCode.enum.INVALID_QUERY;
951951
err.status = 400;
@@ -984,7 +984,7 @@ function uncompilableAggregateFunctionError(func: string): Error {
984984
`correctly and @objectstack/spec AggregationFunction declares it — this is a capability gap ` +
985985
`in the backend, not a mistake in the query, which is why it answers NOT_IMPLEMENTED/501 ` +
986986
`rather than a 400. Aggregate with a function this backend compiles; whether the declaration ` +
987-
`itself should stand is ADR-0049's enforce-or-remove question (#5907).`,
987+
`itself should stand is ADR-0049's enforce-or-remove question.`,
988988
) as Error & { code?: string; status?: number };
989989
err.code = StandardErrorCode.enum.NOT_IMPLEMENTED;
990990
err.status = 501;
@@ -1011,7 +1011,7 @@ function refuseDistinctAggregateWithoutField(func: string): never {
10111011
const err = new Error(
10121012
`Aggregate function "${func}" needs a "field" — there is nothing to deduplicate. ` +
10131013
`COUNT(*) counts rows and is the spelling that takes no field; a distinct count has to name ` +
1014-
`the column whose values are deduplicated. Add "field" to the aggregations[] entry (#6409).`,
1014+
`the column whose values are deduplicated. Add "field" to the aggregations[] entry.`,
10151015
) as Error & { code?: string; status?: number };
10161016
err.code = StandardErrorCode.enum.INVALID_QUERY;
10171017
err.status = 400;
@@ -1068,7 +1068,7 @@ function refusePerAggregationFilter(alias: string): never {
10681068
`Per-aggregation \`filter\` on "${alias}" is not supported by this backend (Turso remote transport). ` +
10691069
`The query is spelled correctly and @objectstack/spec AggregationNodeSchema declares the key — ` +
10701070
`this backend compiles no conditional-aggregate (SQL FILTER (WHERE …) / CASE WHEN) expression ` +
1071-
`for it, so it is refused rather than silently aggregating the UNFILTERED rows (#10413), which ` +
1071+
`for it, so it is refused rather than silently aggregating the UNFILTERED rows, which ` +
10721072
`is why it answers NOT_IMPLEMENTED/501 rather than a 400. \`engine.aggregate\` lowers filtered ` +
10731073
`aggregations in memory for every driver without native support — route the query through the ` +
10741074
`engine, or drop the \`filter\` key.`,
@@ -1105,7 +1105,7 @@ function refuseDateBucketedGroupBy(granularity: string): never {
11051105
`a capability gap in the backend, not a mistake in the query, which is why it answers ` +
11061106
`NOT_IMPLEMENTED/501 rather than a 400. A driver publishes the granularities it buckets ` +
11071107
`natively as \`supports.queryDateGranularity\`; the engine reads that record and buckets ` +
1108-
`in memory for every granularity absent from it, which is always correct (#6212).`,
1108+
`in memory for every granularity absent from it, which is always correct.`,
11091109
) as Error & { code?: string; status?: number };
11101110
err.code = StandardErrorCode.enum.NOT_IMPLEMENTED;
11111111
err.status = 501;
@@ -3853,8 +3853,8 @@ export class RemoteTransport {
38533853
`(with driver-sql, driver-sqlite-wasm and Turso local) compiled IS NULL (anything but false), ` +
38543854
`driver-memory's query path and driver-mongodb compiled IS NOT NULL (anything but true), and ` +
38553855
`driver-memory's matcher dropped the constraint entirely. Note "false" the STRING is truthy, ` +
3856-
`so it landed on the side opposite the false it was written to mean ` +
3857-
`(objectstack#5347, objectstack#5368, #1116).`,
3856+
`so it landed on the side opposite the false it was written to mean. Every driver now ` +
3857+
`refuses it, so one filter no longer gets a different answer per backend.`,
38583858
);
38593859
}
38603860

@@ -3946,12 +3946,12 @@ export class RemoteTransport {
39463946
`[RemoteTransport] Operator "$exists" on field "${field}" requires a boolean comparand (true or ` +
39473947
`false). Received ${shown} (${preview(value)}) at '${object}.${field}'.$exists. ` +
39483948
`@objectstack/spec FieldOperatorsSchema declares $exists as a boolean. It is refused rather ` +
3949-
`than coerced for the same reason $null is (objectstack#5347): a non-boolean lands on ` +
3949+
`than coerced for the same reason $null is: a non-boolean lands on ` +
39503950
`whichever side the backend's two-branch conditional happens to default to, and those ` +
39513951
`defaults point in OPPOSITE directions — this transport's \`=== false\` test compiled ` +
39523952
`IS NOT NULL for anything but false, a \`=== true\` test compiles IS NULL for anything but ` +
39533953
`true. Note "false" the STRING is truthy, so it landed on the side opposite the false it was ` +
3954-
`written to mean (objectstack#5369, objectstack#5903).`,
3954+
`written to mean.`,
39553955
);
39563956
}
39573957

@@ -4156,7 +4156,7 @@ export class RemoteTransport {
41564156
subtree,
41574157
`[RemoteTransport] $between on ${target} must be lowered to $gte/$lte before it reaches the ` +
41584158
`transport — TursoDriver.toRemoteFieldSpec does that so the calendar-day upper-bound rule is ` +
4159-
`applied exactly once (#1003). Refusing rather than compiling a second, rule-free range.`,
4159+
`applied exactly once. Refusing rather than compiling a second, rule-free range.`,
41604160
);
41614161
}
41624162
// [#20020, the #8220 contract] The two arms below name the target and the
@@ -4182,7 +4182,7 @@ export class RemoteTransport {
41824182
subtree,
41834183
`[RemoteTransport] Unsupported filter operator "${op}" on ${target} in remote mode. Supported: ` +
41844184
`${SUPPORTED_FILTER_OPERATORS.join(', ')}. Refusing rather than compiling it to an equality — a ` +
4185-
`silent degradation is indistinguishable from "no rows matched" (#1004).`,
4185+
`silent degradation is indistinguishable from "no rows matched".`,
41864186
);
41874187
}
41884188

@@ -4239,16 +4239,15 @@ export class RemoteTransport {
42394239
subtree,
42404240
`${shared}. "${key}" IS a field operator, one level down: write ` +
42414241
`{ <field>: { "${key}": <value> } } — e.g. { stage: { "${key}": … } } — rather than putting ` +
4242-
`it at the condition level (objectstack#5769, objectstack#5348).`,
4242+
`it at the condition level.`,
42434243
);
42444244
}
42454245
return this.withheldRefusal(
42464246
withheld,
42474247
subtree,
42484248
`${shared}. The Filter Protocol declares no "${key}" at any level; on a read it cost an empty ` +
42494249
`page, and on deleteMany/updateMany the predicate is constantly false or constantly true ` +
4250-
`depending on which side of the comparison the literal lands (objectstack#5769, ` +
4251-
`objectstack#5348).`,
4250+
`depending on which side of the comparison the literal lands.`,
42524251
);
42534252
}
42544253

@@ -4291,7 +4290,7 @@ export class RemoteTransport {
42914290
`filter conditions, but received ${shown} (${preview(value)}). @objectstack/spec ` +
42924291
`FilterConditionSchema declares "${key}" as FilterCondition[]. Refusing rather than falling ` +
42934292
`through to the field path, which read '${key}' as a COLUMN NAME and compiled a predicate ` +
4294-
`against a column that cannot exist (objectstack#5769).`,
4293+
`against a column that cannot exist.`,
42954294
);
42964295
}
42974296

@@ -4439,7 +4438,7 @@ export class RemoteTransport {
44394438
`because the backends disagreed: driver-sql handed it to knex and got a bare "Undefined ` +
44404439
`binding(s)" Error carrying no code, while Turso's remote transport compiled it to IS NULL ` +
44414440
`— so \`{ owner_id: ctx.user?.id }\` with a missing id silently matched every env-wide row ` +
4442-
`instead of failing (#6050).`,
4441+
`instead of failing.`,
44434442
);
44444443
}
44454444

@@ -4490,12 +4489,12 @@ export class RemoteTransport {
44904489
`FilterConditionSchema declares \`$not: FilterConditionSchema\`). Refusing rather than ` +
44914490
`reading '${branch}' as a COLUMN NAME — that is the bug this branch exists to close: it ` +
44924491
`compiled a predicate against a column that cannot exist, and named it as a field of ` +
4493-
`'${object}' when it complained (#1051, #1076). A negation of "no condition" is written ` +
4492+
`'${object}' when it complained. A negation of "no condition" is written ` +
44944493
`\`{ $not: {} }\`, which matches zero rows.`
44954494
: `Every element of ${branch} must be a plain object of conditions (spec ` +
44964495
`FilterConditionSchema). Refusing rather than skipping it — a dropped element leaves ` +
4497-
`valid SQL that answers a DIFFERENT question: narrower in $or, wider in $and (#1004, ` +
4498-
`#1058, #1066, #1073). An intentionally unconstrained branch is written \`{}\`.`;
4496+
`valid SQL that answers a DIFFERENT question: narrower in $or, wider in $and. An ` +
4497+
`intentionally unconstrained branch is written \`{}\`.`;
44994498
// [#20039, the #8220 contract] The position, the kind and the preview are
45004499
// the predicate's (a policy's literal, measured) — and so is which of the
45014500
// three combinators it sat under, since the requirement sentence names it.
@@ -4562,8 +4561,8 @@ export class RemoteTransport {
45624561
`[RemoteTransport] where on '${object}' is ${describeValue(filters)}, not a filter condition: ` +
45634562
`${preview(filters)}. ${requirement} Refusing rather than compiling it to NO WHERE clause — ` +
45644563
`a filter that vanishes WIDENS the statement to the whole table, so a read returns exactly ` +
4565-
`the rows the filter was written to exclude and deleteMany/updateMany touch every row ` +
4566-
`(#1004, #1058, #1066, #1073, #1075). "No filter" is written \`{}\`, or by omitting \`where\`.`,
4564+
`the rows the filter was written to exclude and deleteMany/updateMany touch every row. ` +
4565+
`"No filter" is written \`{}\`, or by omitting \`where\`.`,
45674566
);
45684567
}
45694568

@@ -4601,7 +4600,7 @@ export class RemoteTransport {
46014600
`(${SUPPORTED_FILTER_OPERATORS.join(', ')}); to compare against a value, write the value ` +
46024601
`itself. Refusing rather than dropping the condition — a predicate that vanishes WIDENS the ` +
46034602
`statement to the whole table, so the caller gets back exactly the rows the filter was ` +
4604-
`written to exclude (#1004, #1058, #1066).`,
4603+
`written to exclude.`,
46054604
);
46064605
}
46074606

@@ -4687,9 +4686,9 @@ export class RemoteTransport {
46874686
`compares a column against another column instead of against a value. The query DSL ` +
46884687
`declares this form (spec FieldReferenceSchema) but no executor compiles it, so it is ` +
46894688
`refused here rather than bound as the marker's JSON text — which is valid SQL that ` +
4690-
`matches nothing (#1058). Compare against a literal, or select both columns and compare ` +
4689+
`matches nothing. Compare against a literal, or select both columns and compare ` +
46914690
`after retrieval. The columns and the operator this filter used are withheld from the ` +
4692-
`message (#7929); the full diagnostic is in the server log.`,
4691+
`message; the full diagnostic is in the server log.`,
46934692
diagnostic,
46944693
value,
46954694
`${diagnostic} No executor compiles this form in remote mode — compare against a ` +
@@ -4712,14 +4711,14 @@ export class RemoteTransport {
47124711
`transport cannot bind. A comparison value must be ` +
47134712
`${ACCEPTED_FILTER_COMPARAND_TYPES_SENTENCE}. Refusing rather than binding its JSON text ` +
47144713
`— that compiles to valid SQL matching zero rows, which is indistinguishable from ` +
4715-
`"no rows matched" (#1004, #1058).`;
4714+
`"no rows matched".`;
47164715
this.diagnosticSink?.(diagnostic);
47174716
return withheldInvalidFilterError(
47184717
`[RemoteTransport] A filter comparand in this query is a value this transport cannot bind. ` +
47194718
`A comparison value must be ${ACCEPTED_FILTER_COMPARAND_TYPES_SENTENCE}. Refusing rather ` +
47204719
`than binding its JSON text — that compiles to valid SQL matching zero rows, which is ` +
4721-
`indistinguishable from "no rows matched" (#1004, #1058). The field, the operator and the ` +
4722-
`value this filter used are withheld from the message (#8197); the full diagnostic is in ` +
4720+
`indistinguishable from "no rows matched". The field, the operator and the ` +
4721+
`value this filter used are withheld from the message; the full diagnostic is in ` +
47234722
`the server log.`,
47244723
diagnostic,
47254724
value,

0 commit comments

Comments
 (0)