Skip to content

docs(spec): the $search expansion compiles to $icontains, not an $or of $contains - #15053

Merged
zhuangjianguo merged 2 commits into
mainfrom
claude/issue-13988-search-expansion-icontains-docs
Sep 3, 2026
Merged

docs(spec): the $search expansion compiles to $icontains, not an $or of $contains#15053
zhuangjianguo merged 2 commits into
mainfrom
claude/issue-13988-search-expansion-icontains-docs

Conversation

@claude

@claude claude Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Fixes #13988

Clause ②: no — path limb fires (packages/spec/src/data/driver.zod.ts describe text); content limb no: no accept-set or emitted-shape change.

Triage 5487762327 ruled one card, six sites, one PR. All six land here.


⭐ Site B — the one that is worse than a stale operator name

content/docs/protocol/objectql/query-syntax.mdx, in the Full-Text Search callout, told the reader:

Whether the expansion should emit $icontains instead of $contains — i.e. whether search is case-insensitive by definition — remains a separate open question, and one that can now actually be answered, since both operators mean one thing everywhere.

It does not remain open. #7641 adjudicated it; expandSearchToFilter has compiled $search to $icontains ever since; packages/objectql/src/search-filter.ts:23 carries the ruling verbatim; and content/docs/releases/v17.mdx already published it. The other five sites misspell an operator — this one told a reader in good faith that a settled ruling was theirs to re-open. It now states the settled answer, and names $contains as case-sensitive by contract (#4706 Q2 = A) so the two operators read as different operators rather than two spellings of one.

A second, materially false sentence sat immediately above that callout and is corrected in the same place: the page said the expansion "emits a plain $contains, which is case-sensitive" and that "a user typing acme does not find ACME Corp". That is the reader-facing consequence of the same defect, stated as fact. It now says search is case-insensitive by ruling.

Site C — corrected in the SOURCE, never in the generated pages

The four fullTextSearch rows in content/docs/references/data/ carry ⚠️ AUTO-GENERATED — DO NOT EDIT. Their producer is the tombstone prescription's .describe() in packages/spec/src/data/driver.zod.ts, which is what this PR edits; the four rows were regenerated with pnpm --filter @objectstack/spec check:generated --fix. None of the four .mdx files was hand-edited.

Measured: check:generated reported 1 of 15 artifacts stale — content/docs/references/** — and --fix regenerated only that one. check:api-surface, check:export-origins and check:authorable-surface stayed green, confirming a describe-text change moves no other artifact. All four regenerated rows assert as carrying $icontains, with zero occurrences of the old string remaining.

Sites A — the hand-written pages

query-syntax.mdx (the expansion sentence and the driver-capability clause "every driver already runs $or/$contains", the one triage noted is easier to miss because it reads as a statement about driver capability), data-modeling/queries.mdx, data-modeling/schema-design.mdx.


Every $contains mention on the three pages, classified individually

⛔ No blanket replace. 24 mentions were read one by one; only expansion descriptions changed.

Page · line (pre-change) Mention Class Action
query-syntax 255 operator table row operator's own row unchanged
query-syntax 266, 271, 280 $like is not a spelling of $contains operator's own semantics unchanged
query-syntax 293 "use $contains / $icontains there" operator advice unchanged
query-syntax 306 Case Sensitivity example operator's own example unchanged
query-syntax 340, 346 case-sensitive by ruling (#6682) operator's own ruling unchanged
query-syntax 399–401 $or code example operator's own example unchanged
query-syntax 972 "expands it into an $or of $contains predicates" expansion $icontains
query-syntax 974 "every driver already runs $or/$contains" expansion (driver-capability clause) $icontains
query-syntax 1009 "it emits a plain $contains, which is case-sensitive" expansion → rewritten
query-syntax 1015 "the $contains alignment landed in three steps" operator's own alignment history unchanged
query-syntax 1026 "remains a separate open question" expansion — site B → rewritten
query-syntax 1053 "the expansion stays a single-table $or of $contains" expansion $icontains
query-syntax 1054 "a $contains against one has nothing to scan" expansion predicate over the mirror column $icontains
queries 61 operator table row operator's own row unchanged
queries 496 "$and-of-$or-of-$contains filter (ADR-0061)" expansion $icontains
queries 533 "no driver has a column for $contains to scan" expansion predicate over the mirror column $icontains
schema-design 132–133 "?search=apollo now expands to name $contains …" expansion $icontains
schema-design 140 "a $contains predicate against one has nothing to scan" expansion predicate over the mirror column $icontains

Two classification notes worth a reviewer's attention:

  • query-syntax:1009 was not on the card's site list. The card and the claim named :970-974, :1026-1027 and :1053. Reading every mention one by one surfaced :1009 as a third false expansion description on the same page — and the most directly harmful of them, since it tells the reader in plain words that search is case-sensitive. It is corrected under the same classification rule, not as scope expansion.
  • The three "mirror field" sentences (query-syntax:1054, queries:533, schema-design:140) describe the predicate the expansion emits over a user-authored mirror column, so they follow the expansion. They are not the __search companion clause.

⛔ The deliberate $contains is NOT touched

packages/objectql/src/search-filter.ts:137-148 emits $contains for the normalized __search companion clause. That is correct: the companion column is lowercase by construction and the term is lowercased before comparison, so a case-sensitive operator over two folded values is exact, not a case bug. Its own comment says "Do not align the two."

packages/objectql/** is not in this diff at all — triage flagged a drive-by "unify the operators" sweep as this card's most likely way to do damage, so the exclusion is stated rather than assumed. content/docs/releases/** is likewise untouched: it was already correct, and it is release-owned.

Verification

Run on the final commit, 25e0fcde (git rev-parse --short HEAD after the last commit; the tree is clean and this is the tree every number below was measured on).

  • pnpm --filter @objectstack/spec test463 test files, 12374 tests passed; typecheck clean (tsc --noEmit, check:scripts-typecheck, check:test-typecheck all OK). Both through the shared verify lock, VERDICT command-exit 0.
  • Gate family re-derived from the actual diff with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack (no paths passed — the script takes its own change set), from a tree with no STALE TREE warning after merging current main. It derived 8 changed paths and a family the dispatch clue did not fully cover; the additions were run too, among them check:doc-formula-expressions, check:doc-security-posture, check:objectui-changeset, check:role-word, check:spec-parsed-alias and check:vendor-version-stamps.
  • 68 gates green. Including the whole docs family — check:docs, check:doc-anchors, check:doc-authoring, check:docs-audit-scope, check:docs-redirects, check:docs-single-h1, check:quick-reference-counts — plus check:corpus-claim-drift ("check-corpus-claim-drift: OK, no new claim sites beside a pinned spelling"), check:generated, check:api-surface, check:authorable-surface, check:export-origins, check:llms-txt, check:skill-refs, check:strictness-ledger, check:variant-docs, check:yaml-examples, check:nul-bytes ("scanned 8174 text file(s) … no raw ASCII control bytes"), check:merge-driver, check:published-files, check:pm-half-states and check:changeset-gate-self-tests.
  • 3 gates NOT MEASURED, none of them red — each exited on an unmet prerequisite and says so in its own verdict text: check-dev-prereqs and check:dual-build-cjs-loads need a full-workspace pnpm build (only the spec closure plus four packages were built here), and check-test-completeness needs a saved turbo run test log, which its own text says to record as NOT MEASURED when the family is run locally. CI runs the farm.
  • check:doc-formula-expressions, check:doc-security-posture and check:skill-examples first exited on unbuilt inputs; their packages were built and all three then ran green, so they are measurements rather than gaps.

Exit codes were captured before any pipe throughout, and the verdicts quoted above are the gates' own printed lines.

Hypotheses carried in the dispatch, as measured

  • H1 — --fix moves exactly the four driver rows and nothing else: CONFIRMED. 1 of 15 artifacts stale; three files moved (driver.mdx carries two of the four rows).
  • H2 — check:corpus-claim-drift and the check:docs family stay green: CONFIRMED. No ratchet rule covers this wording; nothing was re-worded to satisfy a gate.
  • H3 — no test pins the old describe text or the old page sentences: CONFIRMED. The sweep ran with a firing positive control ($icontains in search-filter.ts, 8 hits). Its only other hits are correct and unrelated: driver-sql.ts:2646 and sql-driver-json-column-operator-refusal.test.ts:254 describe $or of $contains as the any-of spelling for JSON columns, a different mechanism.

Out-of-scope finding, filed not fixed

The H3 sweep surfaced a fourth site in this family that no existing card covers: packages/spec/src/data/search-fields.ts:9 carries the same false sentence in its module docblock. It is not published (grep 'clauses over exactly this set' content/ returns zero with a firing positive control), so it is outside this card's published-docs thesis, and it is in a different package from the two code cards (#13744 engine.ts, #13984 search-companion.ts). Filed unassigned as #15052 after a full 530-of-530 open-item dedupe enumeration with a firing positive control. It is deliberately not fixed here.

Changeset

@objectstack/spec patch — a published package's describe text changes and reaches the generated reference pages. Documentation only: no accept/reject, emitted-shape or runtime behaviour change.


🤖 Generated with Claude Code

Generated by Claude Code


Generated by Claude Code

The `DriverCapabilities.fullTextSearch` tombstone prescription and five
hand-written passages still described the ADR-0061 search expansion as an
`$or` of `$contains`. `expandSearchToFilter` has emitted `$icontains` since
#7641; `$contains` is contractually case-sensitive (#4706 Q2 = A).

One callout went further than a stale operator name: it told the reader the
case-insensitivity question "remains a separate open question, and one that
can now actually be answered" — inviting a reader acting in good faith to
re-open an adjudicated ruling. It now states the settled answer.

Site C lands in the `.describe()` source, never in the generated `.mdx`; the
four reference rows are regenerated by `check:generated --fix`. Every other
`$contains` mention on the three pages was classified individually — the
operator's own rows, examples and case-sensitivity rulings are correct and are
untouched. `packages/objectql/src/search-filter.ts` is not in this diff: its
`__search` companion clause is deliberately `$contains` and must not be
aligned. `content/docs/releases/` is not touched; it was already correct.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0174WZTU6XcFcS7g2kykC53i
@github-actions github-actions Bot added the size/s label Sep 3, 2026
@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to listnot 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 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 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.

Coarse fallback — 128 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 0fb944b4f6ec7437ee3abb25fcedbde629466347packageMentionDocs.

Which tree this was computed on

This run read content/docs from 47a4dcf81738ebffaf518ca7d6ebb519c78042d8 — the merge of head 25e0fcde8ec512a78054b891a174b5f719d7eb05 into base 0fb944b4f6ec7437ee3abb25fcedbde629466347, 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 47a4dcf81738ebffaf518ca7d6ebb519c78042d8 && git checkout 47a4dcf81738ebffaf518ca7d6ebb519c78042d8
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 0fb944b4f6ec7437ee3abb25fcedbde629466347 25e0fcde8ec512a78054b891a174b5f719d7eb05 && git checkout -B drift-repro 0fb944b4f6ec7437ee3abb25fcedbde629466347 && git merge --no-ff 25e0fcde8ec512a78054b891a174b5f719d7eb05

node scripts/docs-audit/affected-docs.mjs --json 0fb944b4f6ec7437ee3abb25fcedbde629466347

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

Copy link
Copy Markdown
Collaborator

Landing provenance (PM seat domain:spec, session_0174WZTU6XcFcS7g2kykC53i, 2026-09-03T19:32Z) — flipping to ready and enabling auto-merge (squash).


Generated by Claude Code

@zhuangjianguo
zhuangjianguo marked this pull request as ready for review September 3, 2026 19:32
@zhuangjianguo
zhuangjianguo added this pull request to the merge queue Sep 3, 2026
Merged via the queue into main with commit 89eb997 Sep 3, 2026
36 checks passed
@zhuangjianguo
zhuangjianguo deleted the claude/issue-13988-search-expansion-icontains-docs branch September 3, 2026 19:59
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 protocol:data size/s tooling

Projects

None yet

2 participants