From 4e144cfea1c95f0b7cfe7a4f222edab5c7f1b8cb Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 31 Aug 2026 08:08:33 +0000 Subject: [PATCH] fix(spec): drop dead 'status' member from SEARCHABLE_ENUM_TYPES MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 'status' is not a member of the 49-value FieldType enum, so it could never match a real field type — dead vocabulary in search-fields.ts (verified mechanically: FieldType has 49 members, 'status' is absent, 'select' is present; no in-flight plan for a status field type found). Adds a [#13695] pin asserting SEARCHABLE_ENUM_TYPES stays a real FieldType subset, scoped to that one set — it closes the probe gap that let this sit unnoticed (the existing [#6934] pins check the search vocabularies against each other, never against FieldType itself). A parallel ghost-member finding in SEARCH_AUTO_EXCLUDED_TYPES is out of scope for this fix and filed separately. Includes a patch changeset for @objectstack/spec. _Generated by [Claude Code](https://claude.ai/code)_ --- ...searchable-enum-types-dead-status-entry.md | 28 +++++++++++++++++++ packages/spec/src/data/search-fields.test.ts | 24 ++++++++++++++++ packages/spec/src/data/search-fields.ts | 2 +- 3 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 .changeset/searchable-enum-types-dead-status-entry.md diff --git a/.changeset/searchable-enum-types-dead-status-entry.md b/.changeset/searchable-enum-types-dead-status-entry.md new file mode 100644 index 0000000000..a1b5a0fe63 --- /dev/null +++ b/.changeset/searchable-enum-types-dead-status-entry.md @@ -0,0 +1,28 @@ +--- +'@objectstack/spec': patch +--- + +fix(spec): drop the dead `'status'` member from `SEARCHABLE_ENUM_TYPES` (#13695) + +`SEARCHABLE_ENUM_TYPES` in `packages/spec/src/data/search-fields.ts` declared +`new Set(['select', 'status'])`, but `'status'` is not — and has never been — +a member of the 49-value `FieldType` enum. The entry could never match a real +field's `type`, so it changed no accept/reject behaviour and matched no field +in any object: dead vocabulary that invited the next reader to believe a +`status` field type exists. + +Verified before removal (not read): `FieldType.options` has 49 members, +`'status'` is absent, `'select'` is present; no in-flight plan for a `status` +field type exists anywhere in the tree. + +Grade: `patch`, argued rather than defaulted. Not `skip-changeset` — this +touches published package source, not just docs/tests — and not a no-op +either: this closes a real probe gap. The existing `[#6934]` pins in +`search-fields.test.ts` check the four search vocabularies against **each +other** (pairwise disjointness) but never against `FieldType` itself, so a +pure ghost member — matching nothing, rather than overlapping something — +passed every existing pin silently. This PR adds a `[#13695]` pin asserting +`SEARCHABLE_ENUM_TYPES ⊆ FieldType`, scoped to that one set; a parallel ghost +finding in `SEARCH_AUTO_EXCLUDED_TYPES` (`'object'`, `'grid'`, `'geometry'`, +`'encrypted'` are also not `FieldType` members) is filed separately and left +untouched here. diff --git a/packages/spec/src/data/search-fields.test.ts b/packages/spec/src/data/search-fields.test.ts index dfaad188c3..33a54f22d1 100644 --- a/packages/spec/src/data/search-fields.test.ts +++ b/packages/spec/src/data/search-fields.test.ts @@ -11,6 +11,7 @@ import { SEARCHABLE_ENUM_TYPES, SEARCHABLE_TEXTUAL_TYPES, } from './search-fields'; +import { FieldType } from './field.zod'; // --------------------------------------------------------------------------- // [#4483] The auto-default's "lead" field ORDERS the set; it must not ADMIT one. @@ -142,6 +143,29 @@ describe('[#4483] $search auto field set — lead orders, never admits', () => { // two resolution assertions below go red on an overlap independently of them, // and in opposite directions, so no single relaxation can make this pin vacuous. // --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- +// [#13695] `SEARCHABLE_ENUM_TYPES` members must be real `FieldType`s. +// +// The #6934 disjointness pins above check the vocabularies against EACH OTHER +// but never against `FieldType` itself, so a member that matches no real field +// type at all — a pure ghost, distinct from an overlap — passed every existing +// pin silently: `'status'` sat in this set matching nothing, for as long as it +// took a docs sweep to notice by hand. This pin closes that probe gap for the +// enum vocabulary specifically (the one the finding hit); it is deliberately +// NOT extended to `SEARCH_AUTO_EXCLUDED_TYPES`, which is a separate, larger +// finding of its own (`object`/`grid`/`geometry`/`encrypted` are ghosts there +// too) filed out of scope for this fix. +// --------------------------------------------------------------------------- +describe('[#13695] SEARCHABLE_ENUM_TYPES ⊆ FieldType', () => { + const validTypes: ReadonlySet = new Set(FieldType.options); + + it('every member is a real FieldType — no ghost vocabulary entries', () => { + for (const t of SEARCHABLE_ENUM_TYPES) { + expect(validTypes.has(t), `'${t}' is in SEARCHABLE_ENUM_TYPES but not a FieldType member`).toBe(true); + } + }); +}); + describe('[#6934] search type vocabularies are pairwise disjoint', () => { const overlap = (a: ReadonlySet, b: ReadonlySet) => [...a].filter((t) => b.has(t)).sort(); diff --git a/packages/spec/src/data/search-fields.ts b/packages/spec/src/data/search-fields.ts index 3cb122f973..d80e9f8c0b 100644 --- a/packages/spec/src/data/search-fields.ts +++ b/packages/spec/src/data/search-fields.ts @@ -39,7 +39,7 @@ export const SEARCHABLE_TEXTUAL_TYPES: ReadonlySet = new Set([ 'text', 'email', 'phone', 'url', 'autonumber', 'textarea', 'markdown', ]); /** Enumerated types searched by mapping the query to option values via labels. */ -export const SEARCHABLE_ENUM_TYPES: ReadonlySet = new Set(['select', 'status']); +export const SEARCHABLE_ENUM_TYPES: ReadonlySet = new Set(['select']); /** System / audit / heavy fields never auto-included. */ export const SEARCH_AUTO_EXCLUDED_FIELDS: ReadonlySet = new Set([ 'id', '_id', 'created', 'modified', 'created_at', 'updated_at',