Blocked-by: #15661
Related: #14079
Filed by the domain:spec execution seat (session_01M59rPZZFzqhfMUPFqqZTkf, 2026-09-05T07:13Z) as execution lane (2) of the maintainer ruling on #15661 (5548768100, director seat, decision batch #43, verbatim 「同意」), quoted verbatim:
Execution, two lanes, contract-first: (1) domain:spec — door-refusal rows for the refused classes in filter-comparand-type-conformance.ts or a same-shaped sibling table, referencing the existing sets (⛔ no new set), plus the formula-deferred note (Clause-②: yes, @objectstack/spec minor + BREAKING banner: a text operator over a numeric/boolean/temporal/JSON field is now refused at the engine door instead of answering [] or a dialect accident). (2) domain:engine — the door in packages/objectql/src/engine.ts beside INVALID_FIELD, judged against the object's real field map, before any driver dispatch; pins per refused class and per passing class, and the direct-driver path still answering #14079's A row (@objectstack/objectql minor). Sequencing: after PR(s) for #14079 land. Zone 2 (report, not block): whether objectui's filter builder emits such conditions today (the seat cannot measure host apps; say so).
Lane (1) is dispatched on #15661 (spec seat claim on that card). This card is lane (2) and lands in packages/objectql/src/engine.ts — the engine lane's; domain:* routing and the priority are the triage seat's (the director read #15661 as p3: no incident, a loud-vs-silent question). Named reader: the domain:engine execution seat, at dispatch time; the Blocked-by: line above is the unlock scan's — it returns this card to the queue when #15661 closes (the spec rows merged).
The ruled behaviour
A text operator ($contains / $notContains / $startsWith / $endsWith / $icontains / $like / $ilike) over a field whose DECLARED type can never store a string — NUMERIC_VALUE_TYPES ∪ BOOLEAN_VALUE_TYPES ∪ CALENDAR_DATE_TYPES ∪ INSTANT_TYPES ∪ CLOCK_TIME_TYPES ∪ STRUCTURED_JSON_TYPES (existing sets in packages/spec/src/data/field-value.zod.ts) — is refused at the engine's field-aware door with INVALID_FILTER 400 naming the field and its declared type. String-valued classes (STRING_VALUE_TYPES, autonumber, option codes, reference ids) pass. formula is judged only when its declared return type is readable at the seam. Direct driver calls never pass the engine seam and keep answering #14079's option-A row (FILTER_TEXT_CASES, landed in PR #15686 a646120dc).
Landing facts (origin/main b25a5fc32, 2026-09-05T07:11Z; re-locate by symbol)
Deliverables (for the engine seat's dispatch)
Generated by Claude Code
Blocked-by: #15661
Related: #14079
Filed by the
domain:specexecution seat (session_01M59rPZZFzqhfMUPFqqZTkf, 2026-09-05T07:13Z) as execution lane (2) of the maintainer ruling on #15661 (5548768100, director seat, decision batch #43, verbatim 「同意」), quoted verbatim:Lane (1) is dispatched on #15661 (spec seat claim on that card). This card is lane (2) and lands in
packages/objectql/src/engine.ts— the engine lane's;domain:*routing and the priority are the triage seat's (the director read #15661 as p3: no incident, a loud-vs-silent question). Named reader: thedomain:engineexecution seat, at dispatch time; theBlocked-by:line above is the unlock scan's — it returns this card to the queue when #15661 closes (the spec rows merged).The ruled behaviour
A text operator (
$contains/$notContains/$startsWith/$endsWith/$icontains/$like/$ilike) over a field whose DECLARED type can never store a string —NUMERIC_VALUE_TYPES∪BOOLEAN_VALUE_TYPES∪CALENDAR_DATE_TYPES∪INSTANT_TYPES∪CLOCK_TIME_TYPES∪STRUCTURED_JSON_TYPES(existing sets inpackages/spec/src/data/field-value.zod.ts) — is refused at the engine's field-aware door withINVALID_FILTER400 naming the field and its declared type. String-valued classes (STRING_VALUE_TYPES,autonumber, option codes, reference ids) pass.formulais judged only when its declared return type is readable at the seam. Direct driver calls never pass the engine seam and keep answering #14079's option-A row (FILTER_TEXT_CASES, landed in PR #15686a646120dc).Landing facts (
origin/mainb25a5fc32, 2026-09-05T07:11Z; re-locate by symbol)packages/objectql/src/engine.tsraisesINVALID_FIELDfor an unknown field inwhere/$filter(:1098region; docblock:1009-1185) — the ruled door sits beside it, judged against the object's real field map before any driver dispatch. Re-check:git grep -n "err.code = 'INVALID_FIELD'" origin/main -- packages/objectql/src/engine.ts(control: the same file hitsINVALID_FILTER, 2 sites today).packages/objectql/src/engine-comparand-type-door.test.tsreads the spec's comparand conformance table and asserts the engine's door per row — the lane-(1) table (or sibling) is shaped to be consumed the same way.publishBulkDataEventdoes not stamp the batchorganizationIdthe spec now declares (PR #15218) — the bulk producer half of the #13566 p0 cross-tenant webhook leak #15225, finding(objectql): backfillSummaryNulls cannot fill a JUST-CREATED min/max/avg roll-up — summaryNullIsBackfillable decides on the function alone, so "never computed" is indistinguishable from "no child rows" #15064, finding: the insert-pathreadonlystrip is a protocol-boundary guard only —engine.insertapplies none of it, andcreate_record'sonFieldsDroppedchannel can never fire for a readonly drop #14147 declareengine.tsin their file faces;engine.tslast moved on main at3bd9b3498(feat(metadata,objectql): a leaf/view-containersubpath keeps objectql lean under ADR-0076 - no manager, chokidar, glob or js-yaml in the core closure #15343).Deliverables (for the engine seat's dispatch)
INVALID_FIELD: refused classes ⇒INVALID_FILTER400 with the field name and its declared type in the message (the ADR-0112 envelope; assert code + status + message substance, nottoThrow()alone); passing classes unchanged;formuladeferred unless its return type is readable at the seam (measure first, say which).findData(and the RESTwhere/$filterdoors that reach it), plus the direct-driver path still answering the option-A row.@objectstack/objectqlminor changeset;Clause-②: yes(a published behaviour narrows) ⇒needs:contract-reviewon the PR.Fixesthis card; [Decision] refuse a text operator ($containsfamily) over a field whose DECLARED type is not textual —INVALID_FILTER400 at the engine's field-aware door (option C of #14079); the textual-type vocabulary is the question #15661 is referenced verb-less.Generated by Claude Code