You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 6fd87b7
Browse filesBrowse the repository at this point in the historyBrowse files
fix(objectql,metadata-protocol)!: an in-process search-field override that names no searchable field is refused, never widened to a default set (#28120) (#28175)
<!-- claude-projects-attribution: {"github_login":"os-elon-musk"} -->
_Requested by **Elon** · [project
thread](https://claude.ai/code/project/chan_01WedDzV26nniwxv7jC88B9D?thread=cmsg_01WedDzV26nniwxv7jC88B9DN1vFk8uepTZEvr1omkwvCG)_
domain:server#3 · os-elon-musk · session_016WV4cyi29iG8J3UUXr68AZ
Fixes#28120 (family card; folds #28121, closed as folded into it)
Clause-②: no (narrowing) — two in-process tolerances the Data API doors
already refuse are closed: the stored-metadata reader seam stops
narrowing a search-field override that names no field and hands it to
the engine's refusal, and the engine refuses an override naming none of
the fields its search scans. No export, answer member, status or error
code is added; the refusals are the doors' `VALIDATION_ERROR` / 400 and
`INVALID_FIELD` / 400.
Metadata-protocol: none
## 维护者速读
- 改了什么:进程内调用方(host handler、hook、flow)的 `searchFields` 覆盖,凡是 REST
门会拒的两种值,进程内现在也拒:(a) 经存储元数据读缝的空列表,不再被改写成默认集合,交给引擎按 400 `VALIDATION_ERROR`
拒;(b) 全部字段名都不可搜(拼错、改名、内部字段)的覆盖,引擎按 400 `INVALID_FIELD` 拒,不再退回整个默认集合去搜。
- 为什么改:#28108 合同复审的 S1、S3 两条。调用方写覆盖是为了收窄,静默放宽成默认集合是错的。
- 风险与回滚:只拒原本被放宽的值;部分字段名可识别的覆盖、未给覆盖、合法覆盖都照旧。回滚即 revert。
- 先看:`packages/objectql/src/engine.ts` 的
`refuseSearchOverrideNamingNoSearchableField`,`packages/metadata-protocol/src/protocol.ts`
的 `narrowStoredMetadataSearch`。
- 两分钟验证:`pnpm --filter @objectstack/objectql exec vitest run
src/engine-empty-search-fields-refusal.test.ts`,预期 47 passed。
- 需要你做的:无;席位安排契约复审后落地。
## What changes
**(a) The stored-metadata reader seam (`@objectstack/metadata-protocol`,
`narrowStoredMetadataSearch`).** An explicit search-field list that
names no field (an empty array, a blank or comma-only string, an array
with no string entry) is now passed through untouched, so the engine's
own refusal answers it. Before, it was read as "names none", narrowed,
and the narrowed default set was written over the caller's list, so
`searchFields: []` from a host handler on `sys_metadata` /
`sys_metadata_history` was served. The door never reaches this with such
a list: its ingress gate (`assertSearchFieldsAreSearchable`) refuses it
first, unchanged. The docblock now says what is true: an empty override
is refused by the engine, not read as absent.
`refuseOrNarrowStoredMetadataEvaluate` in `packages/runtime` calls this
function and needed no change. A non-string, non-array value is still
narrowed, as before.
**(b) The engine (`@objectstack/objectql`, `expandSearchOnAst`).** A
`searchFields` or object-form `search.fields` override that names none
of the fields the expansion scans is refused `400 INVALID_FIELD`
(`param`, `field` / `fields` naming what the caller wrote, `object`)
before any driver read, on `find`, `findOne`, `count` and `aggregate`.
"Scans" is judged on the set the expansion resolves over
(`resolveSearchFieldResolution` over the fields left after the
`internal: true` and credential withholds), so this refuses exactly the
overrides `resolveSearchFields` used to drop and widen back to the whole
set, including an override naming only unknown fields on the
stored-metadata family, which is now refused rather than resolved to the
default set. Kept as it was:
- an override naming SOME searchable field: the other names are still
intersected away in process (the doors refuse them);
- an object whose allowed set is empty: its own #24284 refusal still
answers, naming that cause;
- an absent or `null` override, and a non-array, non-string value.
**`@objectstack/spec`, `resolveSearchFields`.** Docblock only: it
records that the engine now refuses the fallback's input before this
runs. The function itself is unchanged (it is spec's public helper and
stays non-throwing).
**Docs.** `content/docs/protocol/objectql/query-syntax.mdx`: the
sentence saying direct engine callers keep the tolerant intersection now
says one naming no searchable field is refused.
## Why
Both halves come from the contract review of PR #28108 (record
6523659371, S1 and S3) and share one defect: an in-process override that
the doors refuse was widened to a default set instead of refused. Triage
6524748197 graded it one family card, `Clause-②: no (narrowing)`.
## Verification
Premises measured first on `main` `277275aa` (fix files reset to main
and rebuilt), in-process, through the seam over a real `ObjectQL` with
the real `SysMetadataObject` and a driver double recording each read:
(a) `searchFields: []`, `' , '` and `search.fields: []` through the seam
were served (narrowed set handed to the driver); (b) an all-unknown
override widened to the default set on an ordinary object and on the
family. REST callers: unchanged, refused by
`assertSearchFieldsAreSearchable` (the door-parity cases below).
-
`packages/runtime/src/stored-metadata-reader-seam-search-override.test.ts`
(new, 15, seam over a real engine, `find` / `findOne` / `count`): the
empty array, comma-only string and empty object-form list are refused
`400 VALIDATION_ERROR` before any driver read; an all-unknown override
is refused `400 INVALID_FIELD` before any driver read; controls: a named
override scans only what it names, an absent override is still narrowed
off the body and hash columns.
- `packages/objectql/src/engine-empty-search-fields-refusal.test.ts`
(+32, all four verbs): all-unknown array, comma string, object form and
a list with no string entry are refused `400 INVALID_FIELD` before any
driver read; the refusal names the field; the REST door answers the same
status and code; controls: partly-known override narrows to its known
names, valid and absent overrides answer as before, an object with no
searchable field keeps the #24284 refusal.
- Two existing engine tests pinned the old fallback and now pin the
refusal: `engine-search-empty-set.test.ts` (an override intersected to
nothing) and `internal-field-positions.test.ts` (an override naming only
an `internal: true` column; a partly-known one is still intersected
away, never scanning it).
- Ablations (fix files reset to `main` and rebuilt): seam fix out,
engine fix in ⇒ 6 seam cases red (the empty-list cases), engine pins
green; engine fix out, seam fix in ⇒ 14 engine cases red and 3 seam
cases red (the all-unknown cases); both in ⇒ all green. Each half's pin
bites on its own.
- Package suites at head: objectql, metadata-protocol, runtime green;
`pnpm --filter <pkg> typecheck` green for objectql, metadata-protocol,
runtime, spec. Dogfood battery, every `dispatch-gates --commands` family
and `pnpm lint`: results posted on the card with the os-dev report.
Skills-docs: `git grep -n -i searchFields -- skills content/docs` read
for wording on the engine's tolerance:
`content/docs/protocol/objectql/query-syntax.mdx` said direct engine
callers keep the tolerant intersection, updated here.
`skills/objectstack-query/SKILL.md` describes the REST refusal only,
still true; `content/docs/api/data-api.mdx` describes the door's
history, still true. Generated `content/docs/references/**` untouched.
Out of the order's file surface, left as is: the
`assertSearchFieldsAreSearchable` docblock in `protocol.ts` (~L12546)
explains the door's gate by the engine's old fallback; it stays true of
`resolveSearchFields` itself and is outside
`narrowStoredMetadataSearch`.
## Human Minimum
- [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic.
- [x] 2. Every name I added uses the casing for its kind.
- [ ] 3. No formatter ran; `pnpm lint` passes. (No formatter ran; the
lint reading is posted on the card with the gate battery.)
- [x] 4. One issue, one branch off an up-to-date `main`, one pull
request.
- [x] 5. A change that publishes carries a changeset, and both
declaration lines are filled in here and in the changeset.
- [ ] 6. `packages/spec` touched: its generated artifacts are
regenerated and committed. (Docblock only; the spec artifact gates'
reading is posted on the card with the gate battery.)
- [x] 7. No hand edits to `content/docs/references/`,
`packages/*/CHANGELOG.md` or `content/docs/releases/`.
- [x] 8. Title, body and comments are in English (the 维护者速读 block is the
lane's maintainer summary).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_016WV4cyi29iG8J3UUXr68AZ
---------
Co-authored-by: Claude <noreply@anthropic.com>
fix(objectql,metadata-protocol)!: an in-process search-field override that names no searchable field is refused, never widened to a default set (#28120)
8
+
9
+
Clause-②: no (narrowing) — two in-process tolerances the Data API doors already refuse are closed. The stored-metadata reader seam (`narrowStoredMetadataSearch`) no longer narrows an override that names no field (an empty list, a comma string with no name, a list with no string entry): it passes it to the engine, which refuses it. And the engine (`find`, `findOne`, `count`, `aggregate`) refuses a `searchFields` or object-form `search.fields` that names none of the fields the search scans, where `resolveSearchFields` used to drop the names and scan the whole default set. No export, answer member, status or error code is added: the refusals are the doors' `VALIDATION_ERROR` / 400 and `INVALID_FIELD` / 400. Folds #28121.
10
+
Metadata-protocol: none
11
+
12
+
<!-- adr-0087: not-required (no-migration-prescription) No key of any schema is added, removed, renamed or re-shaped, and no export or type changes: an engine option value the doors already refuse is now refused in process too, with the doors' codes. There is nothing for a migration to rewrite and no tombstone. -->
13
+
14
+
**BREAKING** accept-set narrowing at the in-process search override (`@objectstack/objectql`, `@objectstack/metadata-protocol`).
15
+
16
+
-**Before.** A host handler, hook or flow reading `sys_metadata` / `sys_metadata_history` through the reader seam with `searchFields: []` was served: the seam read the empty list as "names none" and wrote the narrowed default set over it. On any object, a search whose override named no searchable field (every name unknown, unsearchable or withheld) was resolved to the object's default search set.
17
+
-**Now.** The empty override reaches the engine's refusal through the seam: `400 VALIDATION_ERROR`, before any driver read. An override naming no searchable field is refused `400 INVALID_FIELD` (`param: 'searchFields'` or `'search.fields'`, `field` / `fields` naming what the caller wrote) before any driver read, the code the REST door answers for the same value. `mapDataError` serves both with the door's status and code.
18
+
-**Unchanged.** An absent (or `null`) override searches the default set. An override naming no searchable field sent without a `search` is still not judged in process, since nothing is scanned (the door refuses it with or without a search term). A named override narrows as before. An override naming SOME searchable field still has its other names dropped in process (the doors refuse them). An object with no searchable field at all keeps its own refusal (#24284).
19
+
20
+
**The remedy.** Name at least one field the object searches (its `searchableFields`, or the default set), or leave the key out to search the default fields.
0 commit comments