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
fix(spec,lint): a formula field in searchableFields is refused loudly (#6674) (#7103)
* fix(spec,lint): a formula field in searchableFields is refused loudly (#6674)
#4254 closed the fail-open on the unknown-name axis. The same shape survived
one axis over, on names that are perfectly real: the declared branch of
`resolveSearchFieldResolution` filtered by EXISTENCE only, so a `formula`
field declared in `searchableFields` entered the allowed set — and the #4254
ingress gate, which reads that same set, accepted it for exactly that reason.
A formula value is computed on read and no driver materializes a column for
it, so the `$contains` the engine expands `$search` into has nothing to scan.
Measured: 0 rows on driver-memory, 0 rows WITH NO ERROR on driver-sql. The
declaration read as search coverage and delivered none.
- spec (the deciding face): the declared branch filters on existence AND
scannability; new `SEARCH_VIRTUAL_TYPES` / `isVirtualSearchField` are the
one judgment resolution, gate and linter share. The resolution stays
non-throwing — internal callers never pass an ingress, which is why #4254
put the loudness at the ingress.
- metadata-protocol: 400 INVALID_FIELD under its own reason, split out before
the declared/auto branch because both of those messages are wrong for it.
- lint: a build error on the object's own set as well as a view's narrowing,
under the existing `searchable-field-unsearchable` rule.
The storage-not-taste carve-out is kept and pinned by controls in all three
packages: a declared `json` / `lookup` column is still executed, because it
has a column and CAN match. Corpus sweep of objectstack + objectui + cloud:
zero authored declarations affected.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PiRUoQkTSBBmpyXBY3cVn2
* chore(spec): regenerate the api-surface snapshot for the two #6674 exports
`check:api-surface` (inside the TypeScript Type Check job) judged the public
surface "0 breaking, 2 added" and asked for the snapshot. Both additions are
intentional and are the design's centre: `SEARCH_VIRTUAL_TYPES` and
`isVirtualSearchField` are the ONE judgment the spec resolution, the #4254
ingress gate and the linter all read, so that they cannot drift about which
field types have a stored column.
Snapshot delta is exactly those two names in api-surface/data.json.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PiRUoQkTSBBmpyXBY3cVn2
* chore(spec): record the two #6674 exports in the #7090 export-origins baseline
PR #7090 landed `check:export-origins` after this branch was cut, so the merge
of `origin/main` brought a required gate the branch had never satisfied: the
`data` shard was stale for the two exports this PR adds.
`pnpm --filter @objectstack/spec gen:export-origins` — one shard rewritten,
two added lines, both resolving to the single declaration site
`src/data/search-fields.ts`. No re-homed origin and no second origin for an
existing name, so this is not the #4411 dual-source trap the gate warns about;
`check:dual-source-exports` agrees (0 new).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PiRUoQkTSBBmpyXBY3cVn2
---------
Co-authored-by: Claude <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: content/docs/references/data/object.mdx
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -137,7 +137,7 @@ const result = ApiMethod.parse(data);
137
137
|**highlightFields**|`string[]`| optional |[ADR-0085] Ordered most-important fields; first entry wins where only one fits. Drives default columns, cards, previews, detail highlight strip. Renamed from compactLayout. |
138
138
|**stageField**|`string \| false`| optional |[ADR-0085] Lifecycle stage field (linear/ordered), or false to declare the status field non-linear and suppress stage heuristics. Absent = heuristic detection allowed. |
|**searchableFields**|`string[]`| optional | Fields the `$search` query matches against (ADR-0061). Canonical default for the record picker, list quick-search and global search; views may narrow it. When unset, search auto-defaults to the name/title field plus short-text fields. |
140
+
|**searchableFields**|`string[]`| optional | Fields the `$search` query matches against (ADR-0061). Canonical default for the record picker, list quick-search and global search; views may narrow it. When unset, search auto-defaults to the name/title field plus short-text fields. Entries must name a STORED column: a virtual `formula` field is computed on read and materializes no column, so searching it can never match and it is refused (#6674) — mirror the value onto a stored text field and declare that. |
|**sharingModel**|`Enum<'private' \| 'public_read' \| 'public_read_write' \| 'controlled_by_parent'>`| optional | Org-Wide Default record visibility (OWD) for INTERNAL users. Canonical four only (legacy aliases removed, ADR-0090 D4): private (owner-only) \| public_read (everyone reads, owner writes) \| public_read_write (everyone reads+writes) \| controlled_by_parent (derived from the master record). A CUSTOM object that omits this resolves to private at runtime (ADR-0090 D1). |
143
143
|**externalSharingModel**|`Enum<'private' \| 'public_read' \| 'public_read_write' \| 'controlled_by_parent'>`| optional |[ADR-0090 D11] OWD for external (portal/partner) principals. Defaults to private; must be <= sharingModel in openness. |
0 commit comments