docs(spec,drivers,service-analytics): CAST(col AS BLOB) LIKE returning nothing is a compile-option behaviour, not a SQLite fact - #16113
Conversation
…hing is a compile-option behaviour, not a SQLite fact
Both headers rejected `CAST(col AS BLOB) LIKE ?` as a portable case-exact
construct on the strength of one universal claim: that it "was measured to
return NOTHING at all" on SQLite. That claim is false as stated. Whether LIKE
is false for a BLOB operand is fixed when SQLite is COMPILED, by
SQLITE_LIKE_DOESNT_MATCH_BLOBS, and the two SQLite builds this repo ships
disagree about it.
Measured here over the shared FILTER_TEXT_ROWS fixture, with
{ name: { $contains: 'acme' } } compiled to that construct:
better-sqlite3 13.0.3 (SQLite 3.53.4, flag compiled in) -> []
sql.js 1.14.1 (SQLite 3.49.1, flag absent) -> ['1','2']
Neither file's conclusion changes: both still reject the construct, and the
rejection is now stronger rather than merely hedged. A construct that means
two different things on the two builds this repo ships is disqualifying for a
read scope on its own, without needing any particular return value at all --
one build silently answers nothing, the other silently answers exactly the
ASCII over-fold the change was made to end. GLOB, which both files chose
instead, answers ['2'] on both builds.
Comment text only: no behaviour change, no exported surface, and no emitted
JS or .d.ts difference (the driver-side block documents a module-private
function; the analytics one is a file header).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XpTx2tbq3pZRYAdoGt6E6Y
📓 Docs Drift Check1 anchor(s) derived from 4 changed package(s); no hand-written page names any of them. What this run could not see
Coarse fallback — 134 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 2a3d93f2eab8c5d8b11e542600d7497d1d8951bd && git checkout 2a3d93f2eab8c5d8b11e542600d7497d1d8951bd
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 932acc3df41e016812cfc17ffe1527fd7b5df538 990b3d48d926f84339ab8120dc39abb0be68e646 && git checkout -B drift-repro 932acc3df41e016812cfc17ffe1527fd7b5df538 && git merge --no-ff 990b3d48d926f84339ab8120dc39abb0be68e646
node scripts/docs-audit/affected-docs.mjs --json 932acc3df41e016812cfc17ffe1527fd7b5df538 |
…hing is a compile-option behaviour, not a SQLite fact
Both headers rejected `CAST(col AS BLOB) LIKE ?` as a portable case-exact
construct on the strength of one universal claim: that it "was measured to
return NOTHING at all" on SQLite. That claim is false as stated. Whether LIKE
is false for a BLOB operand is fixed when SQLite is COMPILED, by
SQLITE_LIKE_DOESNT_MATCH_BLOBS, and the two SQLite builds this repo ships
disagree about it.
Measured here over the shared FILTER_TEXT_ROWS fixture, with
{ name: { $contains: 'acme' } } compiled to that construct:
better-sqlite3 13.0.3 (SQLite 3.53.4, flag compiled in) -> []
sql.js 1.14.1 (SQLite 3.49.1, flag absent) -> ['1','2']
Neither file's conclusion changes: both still reject the construct, and the
rejection is now stronger rather than merely hedged. A construct that means
two different things on the two builds this repo ships is disqualifying for a
read scope on its own, without needing any particular return value at all --
one build silently answers nothing, the other silently answers exactly the
ASCII over-fold the change was made to end. GLOB, which both files chose
instead, answers ['2'] on both builds.
Comment text only: no behaviour change, no exported surface, and no emitted
JS or .d.ts difference (the driver-side block documents a module-private
function; the analytics one is a file header).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XpTx2tbq3pZRYAdoGt6E6Y
… two remaining carriers The false sentence had FOUR carriers in the tree, not the two originally identified. `git log -S` dates them to one origin: 3172831 2026-08-08 planted it in sql-driver.ts AND remote-transport.ts in the same commit d063a96 2026-08-11 copied it into packages/spec/src/data/filter.zod.ts 54bb2f1 2026-09-05 copied it into service-analytics/text-match-sql.ts One origin, four carriers, four weeks. Correcting only some of them is worse than correcting none: a reader trusts the uncorrected copy precisely because of where it sits, and packages/spec is the contract layer -- it outranks the driver layer as an authority. Both remaining headers now name SQLITE_LIKE_DOESNT_MATCH_BLOBS, carry the two shipped builds' divergent readings, and let the divergence carry the rejection rather than any claim about return values. Neither conclusion is weakened: both still reject the construct and choose GLOB. The spec header is where this bites hardest, and it already said so without knowing it: three of the five backends it lists are SQLite underneath, and those are NOT the same build -- driver-sql runs better-sqlite3 (flag compiled in) and driver-sqlite-wasm runs sql.js (flag absent). The two measured rows ARE two of the three it enumerates. Two readings added to all four headers so the remedy's stability is measured rather than asserted: typeof CAST(name AS BLOB) is 'blob' on BOTH builds -- the CAST is not the part that differs; LIKE's blob rule is the compile-time half GLOB answers ['2'] on BOTH builds -- stable across exactly the axis that disqualifies the rejected construct Comment text only; no behaviour, no exported surface. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XpTx2tbq3pZRYAdoGt6E6Y
…form The branch diverged when this seat rebased onto a newer main AFTER pushing: d534452 (pushed, based on 64011dd) and da0dae9 (its rebased twin, based on a4816a7) carry the same two-site correction with different shas. Merging rather than force-pushing: force-push is barred on this branch, and the PR already points at d534452. Verified before merging that nothing is lost -- text-match-sql.ts is byte-identical between the two, and sql-driver.ts differs only by this round's additive typeof CAST(name AS BLOB) paragraph. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XpTx2tbq3pZRYAdoGt6E6Y # Conflicts: # packages/drivers/driver-sql/src/sql-driver.ts
…blished typings, so it is not skip-changeset Measured on the built tree rather than assumed: the corrected text reaches packages/spec/dist/filter.zod-*.d.ts and .d.mts, and driver-turso's dist/index.d.ts, index.d.mts, index.js and index.mjs. @objectstack/spec also publishes the edited source file itself through its src/**/*.zod.ts files entry. Two packages therefore ship changed declarations, which is a release, so the earlier skip-changeset judgement -- correct while the change was confined to a module-private function and a file header -- does not survive the widened set. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XpTx2tbq3pZRYAdoGt6E6Y
CAST(col AS BLOB) LIKE returning nothing is a compile-option behaviour, not a SQLite factCAST(col AS BLOB) LIKE returning nothing is a compile-option behaviour, not a SQLite fact
Keeps the PR's diff computable against a base that is an ancestor of this head, so the comparison shows only this card's five files. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XpTx2tbq3pZRYAdoGt6E6Y
PM 验收 · 独立复核了三项,全部成立。⭐ 其中一项推翻了我给的口径,而它是对的。复核的三项(本地 git 读数,⛔ 未采信报告转述)① 四个载体全部改到位 ② 那句普遍断言在全树归零。 ③ 变更集恰好 5 个文件,+86/-12:四个载体 + 一个 changeset。
⭐ 你推翻了我的口径,而且理由比我的强我裁定纳入 spec 时,只要求你重新判定 ⭐ 但真正让这条值得记下来的是第二层理由:
⇒ 一个标签可以把一整族门从「绿」变成「看不见」。⭐ 这是本仓那条规则此前没有覆盖到的形态,已收进本席位的常备口径。 changeset 的分级也不是默认出来的:四个包各自 ⭐ 你主动作废的那批读数,是本轮最该表扬的一件事
⛔ 没有报「都过了」,也没有悄悄修掉不提。⇒ 本席位对这一批不作任何采信,也不需要 —— 你已经把它们排除在证据之外了。 未复核的部分(⛔ 按你的报告记,不冒领)71 个门族全 0(42 → 65 → 71 的变动过程)、 状态本 PR 现在 ⛔ 保持 draft、未 arm:按维护者裁决,等总监契约复审,CI 绿本身不是门槛。
Generated by Claude Code |
Fixes #15805
Fixes #16112
Docs precision only across four files. No behaviour changes, no exported surface moves, and no conclusion is weakened — all four sites still reject
CAST(col AS BLOB) LIKE ?and all four still chooseGLOB. What changes is the supporting premise, which was stated as a universal fact about SQLite and is not one.#16112 was filed by this seat mid-round, after a grep showed the sentence had four carriers rather than the two #15805 names. It is absorbed here rather than landed separately, on the PM's ruling: applying #15805's own rule to the corrected population gives four, which is the same rule rather than a widened scope.
The false sentence
Four headers rejected the construct on the strength of one claim — that it "was measured to return NOTHING". Whether
LIKEis false for a BLOB operand is not a property of SQLite the language: it is fixed when SQLite is compiled, bySQLITE_LIKE_DOESNT_MATCH_BLOBS. The two SQLite builds this repo ships disagree about it.Measurement — re-measured by this seat, not carried from the card
Both builds are already dependencies.
{ name: { $contains: 'acme' } }over the sharedFILTER_TEXT_ROWSfixture, compiled asCAST(name AS BLOB) LIKE ?:pragma_compile_optionsLIKE_DOESNT_MATCH_BLOBSpresent[]['1','2']—ACME CorpANDacme corpTwo further readings, now carried in all four headers so the remedy's stability is measured rather than asserted:
typeof CAST(name AS BLOB)is'blob'on both builds — the CAST is not the part that differs;LIKE's blob rule is the compile-time half.name GLOB '*acme*'answers['2']on both builds — stable across exactly the axis that disqualifies the rejected construct.Why this makes the argument stronger
The original premise was a universal claim about return values, which a single counter-example overturns — and this repo ships the counter-example. The replacement needs no particular return value: a construct that means two different things on the two SQLite builds this repo ships — silently answering nothing on one and silently answering exactly the ASCII over-fold the change was made to end on the other — is disqualifying for a read scope (#3948) on its own, because the deciding flag is upstream of us.
The four carriers, and their provenance
git log -Son a non-shallow clone dates the spread to one origin:One origin, four carriers, four weeks.
packages/drivers/driver-sql/src/sql-driver.ts#6518header at 2867-2868 — located and verified here, not carried from a note. Where the sentence originated.packages/drivers/driver-turso/src/remote-transport.tspackages/spec/src/data/filter.zod.tspackages/services/service-analytics/src/text-match-sql.tsConfined to those four comment blocks — no code, no signatures, no tests touched.
Changeset — the two-site verdict was re-decided on the widened set, and reversed
This PR originally carried
skip-changeset. That was correct while the change was confined to a module-private function and a file header. It does not survive addingpackages/specanddriver-turso, so a changeset is included and the label has been removed (confirmed by API read-back; the labels now aredocumentation,tests,tooling,protocol:data).likePatternToGlobPatterninfilter.zod.tsis exported, so its TSDoc reaches published declarations — measured inpackages/spec/dist/filter.zod-DGYUwZPS.d.tsand.d.mts.driver-tursoships it indist/index.d.ts,dist/index.d.mts, and the emitteddist/index.js/dist/index.mjs.@objectstack/specalso publishes the edited source file itself, via itsfilesentrysrc/**/*.zod.ts.⇒ Two packages ship changed declarations, which is a release. For
driver-sqlandservice-analyticsthe change reaches published output only through sourcemaps; they are listed because all four are in onefixedversion group.Leaving the label on would have been worse than a wrong label: it exempts the
changeset-checkjob wholesale, so the changeset gates would have reported skipped — unmeasured, not green.Does the spec docblock reach a generated reference page? No — established, not assumed.
pnpm --filter @objectstack/spec run check:docsexits 0 with the edit in place, reporting227 generated files in sync with packages/spec, andcheck:generatedreportsAll 15 generated artifacts are up to date. Affirmatively:gen:docswas run twice, exit 0 both times, withgit status --porcelainempty after each — zero files rewritten, so the fixpoint holds and nothing generated was hand-edited.Verification
Gate family re-derived from the widened change set — ⛔ the earlier 42 was not reused:
The count moved 42 (two sites) to 65 (adding
packages/spec) to 71 once the changeset existed. Unless noted, figures ran on990b3d48d, the pushed head:dispatch-gates.mjs --commands990b3d48dos-verify-lock.sh -c 'pnpm build'pnpm --filter ... typecheck990b3d48d(each echoed itstsc --noEmit)ecea92c17grep -naPover all 5 changed filesTest totals:
spec482 files / 12950 tests passed;driver-sql155 passed + 9 skipped / 2389 passed + 140 skipped;driver-turso44 files / 1168 tests passed;service-analytics93 files / 2013 tests passed. Thedriver-sqlskips are pre-existing declared MySQL/Postgres cells — unmeasured, not green, and unchanged here. Those suites ran onecea92c17; the only change since is a merge ofmain, whose incoming files (packages/cli,packages/drivers/driver-memory) touch none of the four packages — verified withgit diff --name-only.Six roster-based families whose rosters sit under directories these paths are in — where the derivation warns its own silence is evidence in neither direction — were run explicitly rather than read as clear:
check-changeset-fixed, speccheck:meta-url-spelling, speccheck:spec-changes,check:authz-resolver,check:error-code-casing,check:filter-alias-parity. All exit 0.Every exit code was captured immediately after a single redirected command, ⛔ never through a pipe. One earlier batch of spec-check readings was discarded rather than reported: a malformed substitution turned the command into a pipeline, so
$?was reportingtr's status; those checks were re-run cleanly and are the numbers above.Branch note
A container restart interrupted this round, and the branch had diverged because this seat rebased onto a newer
mainafter pushing — leavingd53445224on the remote and its rebased twin locally. Reconciled by merging the pushed commit rather than force-pushing (force-push is barred here, and the PR already pointed at it). Verified before merging that nothing was lost:text-match-sql.tswas byte-identical between the two, andsql-driver.tsdiffered only by this round's additivetypeofparagraph.That merge left the PR comparing against a stale base, so GitHub briefly attributed 11 files from #16035 to this PR. Merging current
mainin corrected it; the comparison now lists exactly the five files above. Both pushes were fast-forwards (d53445224..ecea92c17,ecea92c17..990b3d48d).⛔ This section reports only what was measured locally. It makes no claim about this PR's CI state, which this seat has not read.
Generated by Claude Code