Skip to content

Commit 8af1a6e

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/issue-14880-dispatch-gates-coverage
2 parents c2e01dd + be41618 commit 8af1a6e

16 files changed

Lines changed: 430 additions & 31 deletions

File tree

.changeset/olive-pugs-shave.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
'@objectstack/spec': patch
3+
---
4+
5+
Correct the `$search` expansion description: it compiles to `$icontains`, not `$contains`
6+
7+
The `DriverCapabilities.fullTextSearch` tombstone prescription in `driver.zod.ts` said
8+
`$search` is compiled into "an `$or` of `$contains` predicates". It has compiled to
9+
`$icontains` since #7641 — textual search is case-insensitive by ruling, and `$contains`
10+
is contractually case-**sensitive** (#4706 Q2 = A). The prescription now says so, and the
11+
four generated driver reference rows it feeds regenerate with it.
12+
13+
The same false sentence is corrected on the three hand-written pages that carried it
14+
(`protocol/objectql/query-syntax.mdx`, `data-modeling/queries.mdx`,
15+
`data-modeling/schema-design.mdx`), including a callout that told readers the
16+
case-insensitivity question "remains a separate open question" when #7641 closed it.
17+
18+
Documentation only — no accept/reject, emitted-shape or runtime behaviour change.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
---
2+
'@objectstack/spec': patch
3+
---
4+
5+
docs(spec): `SpecialOperator` gains the `.describe()` that puts `$null` / `$exists` on the reference page (#14048)
6+
7+
`SpecialOperatorSchema` declared both members as bare `z.boolean().optional()` with a
8+
JSDoc comment and no `.describe()`. `content/docs/references/data/filter.mdx` fills its
9+
Description column from `prop.description` — the JSON-Schema projection of a Zod
10+
`.describe()` — so both cells rendered **empty**, and the published reference page said
11+
nothing whatsoever about the two operators whose meaning was the subject of a six-site
12+
correction campaign (#13539, #13709). A reader could not learn from that page that
13+
`$exists` asks whether the field HAS A VALUE.
14+
15+
Each member now carries a `.describe()`; the JSDoc stays as the source of truth it
16+
already was and the describe restates it. The regenerated page gains exactly two
17+
Description cells (`filter.mdx:138-139`) and nothing else.
18+
19+
Prose only. No accept/reject or shape change: both members were and stay
20+
`z.boolean().optional()`, so the only JSON-Schema delta is a `description` string on two
21+
properties. Verified on a freshly built `dist`: `check:generated` reported
22+
`content/docs/references/**` as the single stale artifact and `--fix` regenerated only
23+
`filter.mdx`; `check:api-surface`, `check:export-origins` and `check:authorable-surface`
24+
stayed green with no new export.
25+
26+
The wording was written against `scripts/check-corpus-claim-drift.mjs` rather than into
27+
it: that shrink-only ratchet watches `content/docs/**` for `$exists` prose, its
28+
`exists-key-presence` row reads `exists-key-presence 4` both before this diff and after
29+
the page was regenerated, so the new prose adds zero claim sites.

.changeset/tricky-pans-repeat.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
'@objectstack/cli': patch
3+
---
4+
5+
`os i18n check --help` now names the whole set of translatable surfaces it reports on, derived from the detector's own source-kind taxonomy instead of a hand-typed sample.
6+
7+
The description named five kinds — `object/field/option/view/action` — against a fifteen-member `CoverageIssue['source']` union that `computeI18nCoverage` passes straight through from the shared extractor walk. A five-of-fifteen sample reads as a scope statement, not an illustration: someone who wanted app navigation or dashboard widgets checked was told the command does objects and fields, and either skipped the gate or went looking for a second tool that does not exist.
8+
9+
The surface list is now built from a `Record<CoverageIssue['source'], string>` map, so a new source kind is a compile error until it is named and is published in `--help` in the same edit. The command also declares an oclif `summary` — the short line the `os i18n --help` command list shows — which drops the parenthetical entirely rather than sampling it.

content/docs/data-modeling/queries.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -493,7 +493,7 @@ The structured form is equivalent and carries the experimental knobs below — `
493493

494494
<Callout type="warn">
495495
Only `query` and `fields` are implemented. The engine expands `search` into a driver-agnostic
496-
`$and`-of-`$or`-of-`$contains` filter (ADR-0061) — `fuzzy`, `operator`, `boost`, `minScore`,
496+
`$and`-of-`$or`-of-`$icontains` filter (ADR-0061) — `fuzzy`, `operator`, `boost`, `minScore`,
497497
`language`, and `highlight` are accepted by `QuerySchema` but read nowhere in
498498
`expandSearchToFilter()` / `normalizeSearch()`, so they have no effect; since #4286 their
499499
`.describe()` markers say so. Multiple search terms are always AND-ed regardless of
@@ -530,7 +530,7 @@ The answer is a **mirror field**: copy the related record's title into a stored
530530
field on this object and declare *that* field searchable. A task list searched by
531531
project name gets a `project_name` text column on `task`, maintained on write and
532532
listed in `task.searchableFields`. It has to be a **stored** field — a `formula`
533-
field is virtual, so no driver has a column for `$contains` to scan. Cross-object
533+
field is virtual, so no driver has a column for `$icontains` to scan. Cross-object
534534
search paths are rejected by design, not pending. Full recipe (the hooks that keep
535535
the mirror fresh, and the lint wording) in [Schema Design → Searching by a related
536536
record's title](/docs/data-modeling/schema-design#searching-by-a-related-records-title--mirror-the-value).

content/docs/data-modeling/schema-design.mdx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -129,15 +129,15 @@ To let users search a task list by project name:
129129
}
130130
```
131131

132-
`?search=apollo` now expands to `name $contains 'apollo' OR project_name
133-
$contains 'apollo'` — one single-table scan, on every driver, with no traversal.
132+
`?search=apollo` now expands to `name $icontains 'apollo' OR project_name
133+
$icontains 'apollo'` — one single-table scan, on every driver, with no traversal.
134134
If the object declares no `searchableFields` at all, a `text` mirror is picked up
135135
by the auto-default anyway; declare the set explicitly when you want to pin it.
136136

137137
<Callout type="warn">
138138
**The mirror must be a stored field — a `formula` field does not work.** A
139-
`formula` field is *virtual*: no driver materializes a column for it, so a
140-
`$contains` predicate against one has nothing to scan (the SQL driver would emit
139+
`formula` field is *virtual*: no driver materializes a column for it, so an
140+
`$icontains` predicate against one has nothing to scan (the SQL driver would emit
141141
a `WHERE` over a column that does not exist). A CEL formula also only reads this
142142
record's own fields (`record.<field>`), so it cannot fetch the related title in
143143
the first place.

content/docs/protocol/objectql/query-syntax.mdx

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -969,9 +969,9 @@ disclose every other tenant's values for that column.
969969
### Full-Text Search
970970

971971
The `search` parameter does **not** reach a full-text index. The engine expands it into
972-
an `$or` of `$contains` predicates across the object's server-resolved searchable fields
972+
an `$or` of `$icontains` predicates across the object's server-resolved searchable fields
973973
(ADR-0061) and deletes `search` from the AST before the driver sees it — every driver
974-
already runs `$or`/`$contains`, so no driver support is needed (which is also why the
974+
already runs `$or`/`$icontains`, so no driver support is needed (which is also why the
975975
old `supports.fullTextSearch` capability bit had no reader and was retired in 17.0.0,
976976
#4634).
977977

@@ -1006,13 +1006,15 @@ because the engine-side intersection alone used to drop the unknown name and fal
10061006
scanning the full searchable set. Internal callers reaching `engine.find()` directly keep
10071007
the tolerant intersection. Multiple whitespace-separated terms are AND-ed and
10081008
fields are OR-ed. Case sensitivity comes from the operator the expansion emits, not
1009-
from the expansion: it emits a plain `$contains`, which is **case-sensitive** by the
1010-
rule in [Case Sensitivity](#case-sensitivity) above. Note what that means for search —
1011-
a user typing `acme` does not find `ACME Corp`. Only `select` / `status` option
1012-
*labels* are matched case-insensitively by the expansion itself.
1009+
from the expansion: it emits `$icontains`, which ignores **ASCII case** by the
1010+
rule in [Case Sensitivity](#case-sensitivity) above. So a user typing `acme` does
1011+
find `ACME Corp` — search is case-insensitive by ruling
1012+
([#7641](https://github.com/objectstack-ai/objectstack/issues/7641)). `select` /
1013+
`status` option *labels* are matched case-insensitively too, by a separate
1014+
label→value mapping in the expansion rather than by the operator.
10131015

10141016
<Callout type="info">
1015-
**Measured today: every driver matches that rule.** The `$contains` alignment
1017+
**Measured today: both operators mean one thing on every driver.** The `$contains` alignment
10161018
landed in three steps —
10171019
[#6518](https://github.com/objectstack-ai/objectstack/issues/6518) made `SqlDriver`
10181020
case-exact per dialect (`GLOB` on the SQLite dialects, `LIKE` unchanged on
@@ -1024,8 +1026,12 @@ a user typing `acme` does not find `ACME Corp`. Only `select` / `status` option
10241026
the divergence this callout warned about is closed, and
10251027
`FILTER_TEXT_CASES` holds all five drivers to it. Whether the expansion should emit
10261028
`$icontains` instead of `$contains` — i.e. whether search is case-insensitive by
1027-
definition — remains a separate open question, and one that can now actually be
1028-
answered, since both operators mean one thing everywhere.
1029+
definition — is **settled, not open**:
1030+
[#7641](https://github.com/objectstack-ai/objectstack/issues/7641) ruled that it should,
1031+
and `expandSearchToFilter` has compiled `$search` to `$icontains` ever since.
1032+
`$contains` stays case-**sensitive** by contract
1033+
([#4706](https://github.com/objectstack-ai/objectstack/issues/4706) Q2 = A) — the two
1034+
are different operators, not two spellings of one.
10291035
</Callout>
10301036
`fuzzy`, `boost`, `operator`, `minScore`, `language`, and `highlight` carry
10311037
`[EXPERIMENTAL — not enforced]` markers (#4286): the schema accepts them, the
@@ -1050,8 +1056,8 @@ The declarative answer is a **mirror field**: copy the related record's title
10501056
into a stored field on this object and declare *that* field searchable — a task
10511057
list searched by project name carries a `project_name` text column on `task`,
10521058
maintained on write and listed in `task.searchableFields`, so the expansion stays
1053-
a single-table `$or` of `$contains`. The mirror must be **stored**: a `formula`
1054-
field is virtual, no driver materializes a column for it, and a `$contains`
1059+
a single-table `$or` of `$icontains`. The mirror must be **stored**: a `formula`
1060+
field is virtual, no driver materializes a column for it, and an `$icontains`
10551061
against one has nothing to scan. Cross-object search paths are rejected by
10561062
design, not pending — see [Schema Design → Searching by a related record's
10571063
title](/docs/data-modeling/schema-design#searching-by-a-related-records-title--mirror-the-value)

content/docs/references/data/driver-nosql.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ const result = AggregationPipelineSchema.parse(data);
180180
| **querySubqueries** | `never` | optional | [REMOVED] `DriverCapabilities.querySubqueries` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. ObjectQL never plans subqueries through a driver, so there was nothing for the bit to switch on. Delete the key. |
181181
| **queryCTE** | `never` | optional | [REMOVED] `DriverCapabilities.queryCTE` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. ObjectQL never plans Common Table Expressions through a driver, so there was nothing for the bit to switch on. Delete the key. |
182182
| **joins** | `never` | optional | [REMOVED] `DriverCapabilities.joins` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Related data is resolved by the engine (lookup expansion over `find()`), not by driver-side JOIN planning — no code consulted the bit. Delete the key. |
183-
| **fullTextSearch** | `never` | optional | [REMOVED] `DriverCapabilities.fullTextSearch` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. `$search` is compiled by the engine into an `$or` of `$contains` predicates over the searchable fields (ADR-0061) and removed from the AST before the driver sees it — no driver-side full-text path exists. Delete the key. |
183+
| **fullTextSearch** | `never` | optional | [REMOVED] `DriverCapabilities.fullTextSearch` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. `$search` is compiled by the engine into an `$or` of `$icontains` predicates over the searchable fields (ADR-0061) and removed from the AST before the driver sees it — no driver-side full-text path exists. The operator is `$icontains`, NOT `$contains`: textual search is case-insensitive by ruling (#7641). Delete the key. |
184184
| **jsonQuery** | `never` | optional | [REMOVED] `DriverCapabilities.jsonQuery` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. No engine path ever branched on driver-side JSON querying. Delete the key. |
185185
| **geospatialQuery** | `never` | optional | [REMOVED] `DriverCapabilities.geospatialQuery` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. No geospatial query path exists in the platform — declaring the bit advertised a capability nothing delivers. Delete the key. |
186186
| **streaming** | `never` | optional | [REMOVED] `DriverCapabilities.streaming` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, and `findStream`, the only read this bit could describe, was itself removed in 17.0.0: nothing ever called it, and two of its three implementations materialised the entire result set before yielding. The bit carried the same defect one level up (`SqlDriver` implemented `findStream` yet declared `streaming: false`; `InMemoryDriver` declared `true` over a full-table read) — which is what zero readers makes inevitable. Page large reads through `find()` with `limit`/`offset`. Delete the key. |

content/docs/references/data/driver-sql.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ const result = DataTypeMappingSchema.parse(data);
9494
| **querySubqueries** | `never` | optional | [REMOVED] `DriverCapabilities.querySubqueries` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. ObjectQL never plans subqueries through a driver, so there was nothing for the bit to switch on. Delete the key. |
9595
| **queryCTE** | `never` | optional | [REMOVED] `DriverCapabilities.queryCTE` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. ObjectQL never plans Common Table Expressions through a driver, so there was nothing for the bit to switch on. Delete the key. |
9696
| **joins** | `never` | optional | [REMOVED] `DriverCapabilities.joins` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Related data is resolved by the engine (lookup expansion over `find()`), not by driver-side JOIN planning — no code consulted the bit. Delete the key. |
97-
| **fullTextSearch** | `never` | optional | [REMOVED] `DriverCapabilities.fullTextSearch` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. `$search` is compiled by the engine into an `$or` of `$contains` predicates over the searchable fields (ADR-0061) and removed from the AST before the driver sees it — no driver-side full-text path exists. Delete the key. |
97+
| **fullTextSearch** | `never` | optional | [REMOVED] `DriverCapabilities.fullTextSearch` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. `$search` is compiled by the engine into an `$or` of `$icontains` predicates over the searchable fields (ADR-0061) and removed from the AST before the driver sees it — no driver-side full-text path exists. The operator is `$icontains`, NOT `$contains`: textual search is case-insensitive by ruling (#7641). Delete the key. |
9898
| **jsonQuery** | `never` | optional | [REMOVED] `DriverCapabilities.jsonQuery` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. No engine path ever branched on driver-side JSON querying. Delete the key. |
9999
| **geospatialQuery** | `never` | optional | [REMOVED] `DriverCapabilities.geospatialQuery` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. No geospatial query path exists in the platform — declaring the bit advertised a capability nothing delivers. Delete the key. |
100100
| **streaming** | `never` | optional | [REMOVED] `DriverCapabilities.streaming` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, and `findStream`, the only read this bit could describe, was itself removed in 17.0.0: nothing ever called it, and two of its three implementations materialised the entire result set before yielding. The bit carried the same defect one level up (`SqlDriver` implemented `findStream` yet declared `streaming: false`; `InMemoryDriver` declared `true` over a full-table read) — which is what zero readers makes inevitable. Page large reads through `find()` with `limit`/`offset`. Delete the key. |

0 commit comments

Comments
 (0)