Skip to content

feat(objectql,spec): execute $search via metadata-driven cross-field filter (ADR-0061 P1+P2)#2134

Merged
xuyushun441-sys merged 1 commit into
mainfrom
feat/search-executor
Jun 21, 2026
Merged

feat(objectql,spec): execute $search via metadata-driven cross-field filter (ADR-0061 P1+P2)#2134
xuyushun441-sys merged 1 commit into
mainfrom
feat/search-executor

Conversation

@xuyushun441-sys

Copy link
Copy Markdown
Contributor

Implements ADR-0061 phases P1 (contract) and P2 (executor). $search was a textbook declared-but-unenforced no-op (defined in spec, sent by every client, executed by no driver — see docs/audits/2026-06-record-search-liveness.md). This wires it end-to-end, server-resolved and driver-agnostic.

P1 — contract

  • spec: add object.searchableFields to ObjectSchemaBase — the canonical, server-side source for which fields $search matches. Classified live in liveness/object.json.

P2 — executor

  • objectql/search-filter.ts (expandSearchToFilter): turns a $search term into { $or: [{ field: { $contains } }] } across server-resolved fields — declared searchableFields → auto-default (name/title + short-text types), excluding system/heavy/enumerated-as-text. Multiple terms AND, fields OR. select/status map the query to stored option values via labels ($in) with a raw-value fallback. An optional $searchFields override is intersected with the allowed set (never client-trusted).
  • Wired into engine.find() after schema resolution and before the driver call, merging with any existing filter via $and. No driver changes — every driver already executes $or/$contains.

Showcase

showcase_account.searchableFields = [name, industry, status, billing_email, tax_id] — searching "retail" now matches by industry, not just name.

Verification

  • search-filter.test.ts — 10 cases (field resolution, auto-default exclusions, declared override, $searchFields subset-validation, label→value $in, multi-term AND/OR, case-insensitive).
  • engine.test.ts$search expands into the where that reaches the driver; ANDs with an existing filter; honours declared searchableFields.
  • Liveness gate green; objectql typecheck clean; app-showcase typecheck clean (only pre-existing connector/objectql module-resolution noise).

Scope

Per ADR-0061, P3 (unify list/lookup/⌘K onto this resolver + objectui $searchFields contract) and P4 (trigram/tsvector indexes, relevance/boost/fuzzy, external engines, server searchAll) are deferred to follow-ups. The renderer $or hack from the "rejected alternatives" is not used.

…filter (ADR-0061 P1+P2)

`$search` was a declared-but-unenforced no-op: defined in spec, sent by every
client surface, executed by no driver. This wires it end-to-end as the ADR-0061
decision specifies — server-resolved, driver-agnostic.

P1 — contract:
- spec: add `object.searchableFields` (canonical searchable-field source) to
  ObjectSchemaBase; classify it live in the liveness ledger.

P2 — executor:
- objectql: `search-filter.ts` (`expandSearchToFilter`) turns a `$search` term
  into a `{ $or: [{ field: { $contains } }] }` filter across server-resolved
  fields — declared `searchableFields` → auto-default (name/title + short-text).
  Multiple terms AND, fields OR; `select`/`status` map the query to option
  values via labels ($in) with a raw-value fallback; an optional `$searchFields`
  override is intersected with the allowed set (never client-trusted).
- Wired into `engine.find()` after schema resolution, before the driver call, so
  every driver (all already execute $or/$contains) honours it with no driver
  change. Merges with any existing filter via $and.

showcase: `showcase_account.searchableFields = [name, industry, status,
billing_email, tax_id]` — searching "retail" now matches by industry, not name.

Tests: `search-filter.test.ts` (10 — field resolution, label→value, multi-term,
override validation) + engine.test ($search expansion reaches the driver where).

Builds on ADR-0061. Next: P3 (unify list/lookup/⌘K + objectui `$searchFields`
contract) and P4 (trigram/relevance/external) are deferred per the ADR.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 21, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
spec Ready Ready Preview, Comment Jun 21, 2026 3:06pm

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/objectql, @objectstack/spec.

93 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/concepts/architecture.mdx (via @objectstack/spec)
  • content/docs/concepts/cloud-artifact-api.mdx (via packages/spec)
  • content/docs/concepts/cluster-semantics.mdx (via @objectstack/spec)
  • content/docs/concepts/core/services.mdx (via @objectstack/objectql)
  • content/docs/concepts/design-principles.mdx (via packages/spec)
  • content/docs/concepts/implementation-status.mdx (via @objectstack/objectql, @objectstack/spec)
  • content/docs/concepts/index.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx (via @objectstack/objectql, packages/spec)
  • content/docs/concepts/north-star.mdx (via packages/spec)
  • content/docs/concepts/packages.mdx (via @objectstack/objectql, @objectstack/spec)
  • content/docs/concepts/setup-app.mdx (via @objectstack/spec)
  • content/docs/concepts/skills.mdx (via @objectstack/spec)
  • content/docs/concepts/webhook-delivery.mdx (via @objectstack/spec)
  • content/docs/getting-started/architecture.mdx (via @objectstack/spec)
  • content/docs/getting-started/cli.mdx (via @objectstack/spec)
  • content/docs/getting-started/core-concepts.mdx (via @objectstack/spec)
  • content/docs/getting-started/examples.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx (via @objectstack/spec)
  • content/docs/guides/adding-a-metadata-type.mdx (via @objectstack/spec)
  • content/docs/guides/ai-capabilities.mdx (via @objectstack/spec)
  • content/docs/guides/airtable-dashboard-analysis.mdx (via @objectstack/spec)
  • content/docs/guides/analytics-datasets.mdx (via @objectstack/spec)
  • content/docs/guides/api-reference.mdx (via @objectstack/spec)
  • content/docs/guides/authentication.mdx (via @objectstack/objectql)
  • content/docs/guides/business-logic.mdx (via @objectstack/spec)
  • content/docs/guides/cheatsheets/backward-compatibility.mdx (via @objectstack/spec)
  • content/docs/guides/cheatsheets/error-catalog.mdx (via @objectstack/spec)
  • content/docs/guides/cheatsheets/field-type-gallery.mdx (via @objectstack/spec)
  • content/docs/guides/cheatsheets/field-validation-rules.mdx (via @objectstack/spec)
  • content/docs/guides/cheatsheets/permissions-matrix.mdx (via @objectstack/spec)
  • content/docs/guides/cheatsheets/protocol-diagram.mdx (via packages/spec)
  • content/docs/guides/cheatsheets/query-cheat-sheet.mdx (via @objectstack/spec)
  • content/docs/guides/cheatsheets/quick-reference.mdx (via @objectstack/spec)
  • content/docs/guides/client-sdk.mdx (via @objectstack/spec)
  • content/docs/guides/common-patterns.mdx (via @objectstack/spec)
  • content/docs/guides/contracts/auth-service.mdx (via packages/spec)
  • content/docs/guides/contracts/cache-service.mdx (via packages/spec)
  • content/docs/guides/contracts/data-engine.mdx (via @objectstack/spec)
  • content/docs/guides/contracts/index.mdx (via @objectstack/spec)
  • content/docs/guides/contracts/metadata-service.mdx (via packages/spec)
  • content/docs/guides/contracts/storage-service.mdx (via packages/spec)
  • content/docs/guides/data-modeling.mdx (via @objectstack/spec)
  • content/docs/guides/deployment-vercel.mdx (via @objectstack/objectql, @objectstack/spec)
  • content/docs/guides/driver-configuration.mdx (via @objectstack/spec)
  • content/docs/guides/error-handling-client.mdx (via @objectstack/spec)
  • content/docs/guides/error-handling-server.mdx (via @objectstack/spec)
  • content/docs/guides/formula.mdx (via packages/objectql, @objectstack/spec)
  • content/docs/guides/hook-bodies.mdx (via packages/spec)
  • content/docs/guides/kernel-services.mdx (via @objectstack/objectql, @objectstack/spec)
  • content/docs/guides/metadata/dashboard.mdx (via @objectstack/spec)
  • content/docs/guides/metadata/field.mdx (via @objectstack/spec)
  • content/docs/guides/metadata/flow.mdx (via @objectstack/spec)
  • content/docs/guides/metadata/index.mdx (via @objectstack/spec)
  • content/docs/guides/metadata/object.mdx (via @objectstack/spec)
  • content/docs/guides/metadata/validation.mdx (via @objectstack/spec)
  • content/docs/guides/metadata/workflow.mdx (via @objectstack/spec)
  • content/docs/guides/objectql-migration.mdx (via @objectstack/objectql)
  • content/docs/guides/packages.mdx (via @objectstack/objectql, @objectstack/spec)
  • content/docs/guides/plugin-development.mdx (via @objectstack/spec)
  • content/docs/guides/plugins.mdx (via @objectstack/objectql, @objectstack/spec)
  • content/docs/guides/project-scoping.mdx (via @objectstack/spec)
  • content/docs/guides/public-forms.mdx (via @objectstack/spec)
  • content/docs/guides/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/guides/runtime-services/index.mdx (via packages/spec)
  • content/docs/guides/runtime-services/queue-service.mdx (via packages/spec)
  • content/docs/guides/runtime-services/sharing-service.mdx (via packages/spec)
  • content/docs/guides/runtime-services/storage-service.mdx (via packages/spec)
  • content/docs/guides/security.mdx (via @objectstack/spec)
  • content/docs/guides/seed-data.mdx (via @objectstack/spec)
  • content/docs/guides/skills.mdx (via @objectstack/spec)
  • content/docs/guides/standards.mdx (via @objectstack/spec)
  • content/docs/guides/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx (via @objectstack/spec)
  • content/docs/protocol/objectos/config-resolution.mdx (via @objectstack/spec)
  • content/docs/protocol/objectos/i18n-standard.mdx (via @objectstack/spec)
  • content/docs/protocol/objectos/index.mdx (via @objectstack/objectql)
  • content/docs/protocol/objectos/lifecycle.mdx (via @objectstack/spec)
  • content/docs/protocol/objectos/plugin-spec.mdx (via @objectstack/spec)
  • content/docs/protocol/objectos/runtime-capabilities.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx (via packages/spec)
  • content/docs/protocol/objectql/query-syntax.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx (via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/objectql, @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx (via packages/spec)
  • content/docs/protocol/objectui/record-alert.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx (via @objectstack/spec)
  • content/docs/releases/index.mdx (via @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/objectql, @objectstack/spec)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@xuyushun441-sys
xuyushun441-sys merged commit aaa859b into main Jun 21, 2026
17 checks passed
@xuyushun441-sys
xuyushun441-sys deleted the feat/search-executor branch June 21, 2026 15:14
xuyushun441-sys added a commit that referenced this pull request Jun 21, 2026
…ecutor runs (ADR-0061) (#2142)

Follow-up to the $search executor (#2134): over the REST/OData path the term
never reached engine.find(). protocol.findData() normalizes $-prefixed OData
params ($top/$skip/$orderby/$select/$count) to bare names, but NOT $search /
$searchFields — so `$search=retail` fell through to the implicit-field-filter
pass and became `where.$search = 'retail'` (a non-existent column), which the
driver silently returns [] for. The engine's expandSearchToFilter (which reads
ast.search) therefore never fired.

- Add ['$search','search'] and ['$searchFields','searchFields'] to the
  dollar→bare normalization, and add 'searchFields' to knownParams so the bare
  form isn't treated as an implicit filter.
- Test: findData normalizes $search/$searchFields to bare and never leaks them
  into where.

Verified end-to-end over the real HTTP API against a dev backend: searching
"retail" returns the 3 retail-industry accounts (matched by the industry
label→value), "technology" → 4, "Contoso" → 1 by name, "zzqq" → 0.

Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants