|
| 1 | +--- |
| 2 | +"@objectstack/lint": minor |
| 3 | +--- |
| 4 | + |
| 5 | +feat(lint): a `searchableFields` entry naming no field is caught at authoring |
| 6 | +time, not at request time |
| 7 | + |
| 8 | +`searchableFields` is `z.array(z.string())` in both `object.zod.ts` and the |
| 9 | +list-view schema, so nothing ever checked that an entry resolves to anything. |
| 10 | +Rename a field and the old name stays behind — Zod-valid, shipped, pointing at |
| 11 | +a column that no longer exists. |
| 12 | + |
| 13 | +The engine tolerates it, which is exactly what kept the drift invisible: |
| 14 | +`resolveSearchFields` filters the declaration down to fields that exist |
| 15 | +(`searchableFields?.filter((f) => all[f])`) and says nothing. The tolerance |
| 16 | +fails in the direction nobody expects: |
| 17 | + |
| 18 | +- **some entries stale** → `$search` scans a NARROWER set than the object |
| 19 | + declares. Records that should match do not, and the response is |
| 20 | + indistinguishable from "no such record"; |
| 21 | +- **every entry stale** → the filtered set is empty, so resolution falls |
| 22 | + through to the AUTO-DEFAULT (name/title + short-text fields). A declaration |
| 23 | + whose whole purpose is to CHOOSE the searchable set ends up selecting one the |
| 24 | + author never wrote — the "asked narrower, answered wider" inversion #4226 |
| 25 | + closed on the projection axis. |
| 26 | + |
| 27 | +It also stops being quiet downstream. Clients echo the declaration verbatim as |
| 28 | +the `$searchFields` override (objectui's list search sends |
| 29 | +`schema.searchableFields`), so once the REST read path validates that override |
| 30 | +against the object (#4254), a stale entry the engine had been silently skipping |
| 31 | +becomes a `400 INVALID_FIELD` on every list search for that object — a |
| 32 | +request-time break whose cause is an authoring typo made long before. |
| 33 | + |
| 34 | +**New rule — `searchable-field-unknown` (gating).** Wired into |
| 35 | +`REFERENCE_INTEGRITY_RULES`, so it runs on `os validate`, `os lint` and |
| 36 | +`os compile` with no CLI edit. It covers the object's own ADR-0061 declaration |
| 37 | +and the list views that narrow it (`objects[].listViews`, a `defineView` |
| 38 | +default `list`, and named `listViews`), resolving each entry against the bound |
| 39 | +object's declared fields. |
| 40 | + |
| 41 | +`error`, not the advisory level the other field-existence rules use |
| 42 | +(`page-field-unknown`, `form-field-unknown`, `semantic-role-field-unknown` are |
| 43 | +all warnings). Those describe a consumer that SKIPS an unknown name and renders |
| 44 | +the rest; this describes a declaration that either selects the wrong set or |
| 45 | +refuses the request outright — the same call `validate-flow-template-paths` |
| 46 | +makes for a filter-position token, where the miss widens the query instead of |
| 47 | +shrinking the page. |
| 48 | + |
| 49 | +Existence only: a field that exists but is an odd search target (a `json` |
| 50 | +column) is NOT flagged — an explicit `searchableFields` is authoritative, so |
| 51 | +declaring one is a choice, not drift. Three skips keep false positives near zero |
| 52 | +(ADR-0072 D1): an object this stack does not define, an object with no authored |
| 53 | +field map (external / datasource-introspected), and registry-injected system |
| 54 | +columns — the last derived from the spec's own `FIELD_GROUP_SYSTEM_FIELDS` and |
| 55 | +`SystemFieldName` rather than hand-copied, since this package already carries |
| 56 | +five slightly-different copies of that list. |
| 57 | + |
| 58 | +Dotted paths are the one place this rule is stricter than its siblings. They |
| 59 | +skip `owner_id.name` because the query engine resolves the traversal; search |
| 60 | +does not — `resolveSearchFields` matches the field map by exact string, so a |
| 61 | +dotted entry is dropped exactly like a typo, and it is the spelling most likely |
| 62 | +borrowed from `select`/`sort`. It is flagged, with its own fix hint. |
0 commit comments