Related: #14079
Filed by the domain:spec execution seat (session_01M59rPZZFzqhfMUPFqqZTkf, 2026-09-05T02:03Z) on the director seat's recorded maintainer ruling on #14079 (5548481659, decision batch #39, reply 「同意」), quoted verbatim:
C is deferred, not rejected. A door that refuses a text operator over a field whose DECLARED type is not textual (INVALID_FILTER 400 at the engine's field-aware seam, the comparand-door shape) is the loud answer ③ argues for, and A does not foreclose it. What it needs first is a vocabulary ruling — which of select / lookup / formula / json / code / password (and the rest) count as textual beyond SEARCHABLE_TEXTUAL_TYPES — and that is not a rider on a p2 defect card. Spec seat: file the C card (domain:spec, needs-user-decision, with the dev's option-C text and the type list to adjudicate, Related: #14079), and link it here.
Labels are as that instruction names them. ⛔ Not dispatchable: the decision is the maintainer's. Sequencing: #14079 (ruled A, in flight) lands first and is not blocked by this card; A's contract row stays beneath any door (direct driver calls never pass the engine seam).
Premise, with re-check commands (positive controls named)
origin/main b4b37e59 (2026-09-05T01:51Z). The only real producer of a text operator over a non-textual field already refuses at its own door: global search's $searchFields allow-list is SEARCHABLE_TEXTUAL_TYPES ∪ SEARCHABLE_ENUM_TYPES — re-check git grep -n "SEARCHABLE_TEXTUAL_TYPES" origin/main -- packages/spec/src/data/search-fields.ts (control: the same file must also hit SEARCH_AUTO_EXCLUDED_FIELDS).
- The spec already declares semantic value classes over
FieldType — re-check git grep -n "VALUE_TYPES: ReadonlySet" origin/main -- packages/spec/src/data/field-value.zod.ts (control: the same file hits MULTI_CAPABLE_TYPES). Measured members at b4b37e59:
| set |
members |
STRING_VALUE_TYPES |
text · textarea · email · url · phone · password · secret · markdown · html · richtext · code · color · signature · qrcode |
NUMERIC_VALUE_TYPES |
number · currency · percent · rating · slider · progress · summary |
BOOLEAN_VALUE_TYPES |
boolean · toggle |
CALENDAR_DATE_TYPES / INSTANT_TYPES / CLOCK_TIME_TYPES |
date / datetime / time |
SINGLE_OPTION_TYPES / MULTI_OPTION_TYPES |
select · radio / multiselect · checkboxes · tags |
REFERENCE_VALUE_TYPES |
lookup · master_detail · user · tree (record-id string in stored form) |
FILE_REFERENCE_TYPES |
image · file · avatar · video · audio (opaque id/url string OR inline object today; ADR-0104 D3 pending) |
STRUCTURED_JSON_TYPES |
json · composite · repeater · record · location · address · vector |
COMPUTED_VALUE_TYPES |
formula · summary · autonumber |
⇒ The vocabulary the door needs may already exist: "declared type is not textual" can be spelled from these sets without minting a new one. SEARCHABLE_TEXTUAL_TYPES (text · email · phone · url · autonumber · textarea · markdown) answers a narrower question — what global search auto-scans — and is the wrong set to borrow for a filter door. |
|
- The measured faces (dev report
5542868802 on c550bafc2, seat review 5542938268): JS faces + Mongo type-gate, the SQLite family coerces, live Postgres 17.10 refuses with SQLSTATE 42883 (a 500). Ruling A turns all of that into one declared row; what A leaves is that a mistaken query answers [] silently.
- The door precedent:
packages/spec/src/data/filter-comparand-type-conformance.ts carries verdict: 'door-refusal' rows with code: 'INVALID_FILTER' (re-check git grep -n "door-refusal" origin/main -- packages/spec/src/data/filter-comparand-type-conformance.ts), and packages/objectql/src/engine.ts already judges an unknown field against the object's real field map (INVALID_FIELD, 400 — re-check git grep -n "INVALID_FIELD" origin/main -- packages/objectql/src/engine.ts).
The dev's option-C text (verbatim from 5542868802)
C — REFUSE AT THE DOOR: a text operator over a field whose DECLARED type is not textual is refused before any driver runs — INVALID_FILTER / 400 in the ADR-0112 envelope, at the engine's field-aware seam (packages/objectql/src/engine.ts, beside the INVALID_FIELD unknown-field door that already judges against the object's real field map), exactly as the comparand-type door refuses $icontains: 42 and as $searchFields already refuses a non-textual field. Postgres's measured 500 becomes a declared 400; the evaluators no door fronts (formula's write-side check, having, RLS) keep option A beneath it as defense in depth — the same door-plus-total-evaluator shape FILTER_COMPARAND_TYPE_CASES uses. Contract shape: a door-refusal-verdict row (the comparand table's discriminant), not a FilterTextRowsCase — plus A's evaluated row for the faces behind no door. Cost that must be priced: WHICH field types count as textual is a vocabulary decision (SEARCHABLE_TEXTUAL_TYPES = text/email/phone/url/autonumber/textarea/markdown exists; select/lookup/formula/json/code/password must each be judged), and the engine seam must see the object schema on every read path (the REST and SDK paths do; a direct driver call does not, which is why A stays underneath).
The type list to adjudicate
| class (existing set) |
stored value |
text operator over it |
proposed disposition |
STRING_VALUE_TYPES |
string |
meaningful |
textual — passes the door |
autonumber (computed) |
string |
meaningful |
textual — passes |
select / radio |
one option CODE (string) |
substring over a code: legal today, rarely intended |
passes (codes are strings); ⚠️ password / secret are strings too — filtering a secret by substring is a security posture question, out of this card |
multiselect / checkboxes / tags |
array of strings |
element-wise semantics are their own question (JSON columns) |
not decided here — stays as today |
lookup / master_detail / user / tree |
record-id string |
legal, a smell |
passes (string) |
formula |
depends on the formula's declared return type |
unknown until the return type is read |
⚠️ open — the door can only judge it if the declared return type is available at the seam |
NUMERIC · BOOLEAN · date · datetime · time |
never a string by declaration |
can never match ⇒ the card's {n: 5} cell |
refused at the door under any C |
STRUCTURED_JSON_TYPES |
object |
meaningless |
refused under C-deny |
FILE_REFERENCE_TYPES |
string OR object today |
ambiguous until ADR-0104 D3 |
not decided here |
Options × real cost
| option |
what |
real cost |
C-deny — refuse only the classes whose stored value is never a string by declaration (NUMERIC ∪ BOOLEAN ∪ CALENDAR_DATE ∪ INSTANT ∪ CLOCK_TIME ∪ STRUCTURED_JSON, all existing sets) |
one door-refusal row family in spec, one door in objectql/src/engine.ts beside INVALID_FIELD; zero new vocabulary |
two-lane execution (spec row + engine door); a mistaken query becomes a 400 naming the field's declared type; formula stays undecided until its return type is readable at the seam |
C-allow — refuse everything outside STRING_VALUE_TYPES ∪ {autonumber} |
strictest |
breaks substring filters over select codes, lookup ids, tags that WORK today — needs a measured census of real emitters (objectui filter builder, apps) before it could be adopted; not measurable from this repo alone |
| D — no door; A alone |
nothing beyond #14079 |
a mistaken $contains over a number answers [] with no signal (the "silent wrong answer" the conformance file header names); zero mechanism added |
四维分析(从业务角度)
一句话问题:客户的报表筛选器对「金额」列写了「包含 5」—— 平台是该悄悄返回空表,还是当场告诉他「金额不是文本,这个条件永远匹配不到」?
- 实际业务需求 — 零拉动(实测):全仓没有生产者对数值字段写文本算子;唯一真实生产者(全局搜索)在
$searchFields 门口已拒绝非文本字段。本卡决定的只是「写错的条件」答什么。⛔ 未测:objectui 的筛选器构造器与客户应用是否会发出这种条件 —— 那是本仓看不见的读数。
- 项目长远合理性(权重 ≥50%) — 按本义读(缩小特例、不增契约):C-deny 是收窄接受集(把「永远匹配不到」的条件从静默变成具名拒绝),复用
field-value.zod.ts 已声明的值域类,不新增公开词表;它与 comparand 门、$searchFields 门同构 —— 平台已在两个相邻缝上跑这个形状。代价是多一道门(机制),不是多一套词表。D 零机制,但把「声明 ≠ 执行」留在一个已知格子上。
- 防 AI 写代码犯错 — 最硬指向 C:AI 生成的筛选条件类型写错时,A 之下得到
[] 零信号;C-deny 之下得到 400 + 字段声明类型,错误在写的那一刻被点名。
- 创业阶段不扩散 — C-allow 会打破今天能用的 select / lookup / tags 子串筛选,⛔ 不可荐;C-deny 无新词表、无新配置面,但仍是新增机制;D 最紧。零拉动 ⇒ 默认不扩散。
推荐
D 先行(A 独立成立,#14079 落地即完整),C-deny 作为记录在案的形状,出现真实拉动即重开 —— 依据分歧推荐序:②零拉动 ⇒ 荐不扩散;①长远看 C-deny 是对的形状(收窄、复用已声明类、同构于既有门),因此回退项就是 C-deny 而不是 C-allow;③是唯一逆向的一轴,它压不过①②④合计。重开触发写在关单评论:某个真实生产者(objectui 筛选器、客户应用、或一次支持工单)对非文本声明字段发出文本算子。
若维护者裁 C-deny:两车道执行 —— domain:spec 在 comparand 表或同形姊妹表声明 door-refusal 行(引用既有集合,⛔ 不新铸集合);domain:engine 在 objectql/src/engine.ts 的 INVALID_FIELD 门旁实现;A 的行留在门下;formula 单列为「返回类型可读时再判」。
置信缺口:objectui / 客户应用是否发出此类条件未测;formula 字段的声明返回类型在引擎缝上是否可读未测;MySQL、turso remote、真 mongod 三面(A 的)仍只有源码读数。
os-decision-facets
- ① 长远合理性(≥50%):C-deny 收窄接受集、复用
field-value.zod.ts 既有值域类、与 comparand 门/$searchFields 门同构 ⇒ 若建门则 C-deny;但 A 已使各面同答一行,余下缺口只是「写错的条件静默为空」。
- ② 实际业务拉动:零 —— 无生产者,唯一真实生产者已在门口拒绝 ⇒ 指向 D(不扩散、记录在案)。
- ③ 防 AI 犯错:指向 C-deny —— 400 点名字段声明类型 vs
[] 零信号。
- ④ 创业阶段不扩散:C-allow ⛔(打破现有可用筛选);C-deny 无新词表但新增机制;D 最紧。
- 推荐:D 先行,C-deny 为记录在案的回退形状(重开条件:真实生产者出现);⛔ C-allow 不荐。
- 置信缺口:objectui/客户应用发出面未测;
formula 返回类型在引擎缝可读性未测;MySQL · turso remote · 真 mongod 三面未实测。
Related: #14079
Filed by the
domain:specexecution seat (session_01M59rPZZFzqhfMUPFqqZTkf, 2026-09-05T02:03Z) on the director seat's recorded maintainer ruling on #14079 (5548481659, decision batch #39, reply 「同意」), quoted verbatim:Labels are as that instruction names them. ⛔ Not dispatchable: the decision is the maintainer's. Sequencing: #14079 (ruled A, in flight) lands first and is not blocked by this card; A's contract row stays beneath any door (direct driver calls never pass the engine seam).
Premise, with re-check commands (positive controls named)
origin/mainb4b37e59(2026-09-05T01:51Z). The only real producer of a text operator over a non-textual field already refuses at its own door: global search's$searchFieldsallow-list isSEARCHABLE_TEXTUAL_TYPES ∪ SEARCHABLE_ENUM_TYPES— re-checkgit grep -n "SEARCHABLE_TEXTUAL_TYPES" origin/main -- packages/spec/src/data/search-fields.ts(control: the same file must also hitSEARCH_AUTO_EXCLUDED_FIELDS).FieldType— re-checkgit grep -n "VALUE_TYPES: ReadonlySet" origin/main -- packages/spec/src/data/field-value.zod.ts(control: the same file hitsMULTI_CAPABLE_TYPES). Measured members atb4b37e59:STRING_VALUE_TYPESNUMERIC_VALUE_TYPESBOOLEAN_VALUE_TYPESCALENDAR_DATE_TYPES/INSTANT_TYPES/CLOCK_TIME_TYPESSINGLE_OPTION_TYPES/MULTI_OPTION_TYPESREFERENCE_VALUE_TYPESFILE_REFERENCE_TYPESSTRUCTURED_JSON_TYPESCOMPUTED_VALUE_TYPESSEARCHABLE_TEXTUAL_TYPES(text · email · phone · url · autonumber · textarea · markdown) answers a narrower question — what global search auto-scans — and is the wrong set to borrow for a filter door.5542868802onc550bafc2, seat review5542938268): JS faces + Mongo type-gate, the SQLite family coerces, live Postgres 17.10 refuses with SQLSTATE 42883 (a 500). Ruling A turns all of that into one declared row; what A leaves is that a mistaken query answers[]silently.packages/spec/src/data/filter-comparand-type-conformance.tscarriesverdict: 'door-refusal'rows withcode: 'INVALID_FILTER'(re-checkgit grep -n "door-refusal" origin/main -- packages/spec/src/data/filter-comparand-type-conformance.ts), andpackages/objectql/src/engine.tsalready judges an unknown field against the object's real field map (INVALID_FIELD, 400 — re-checkgit grep -n "INVALID_FIELD" origin/main -- packages/objectql/src/engine.ts).The dev's option-C text (verbatim from
5542868802)The type list to adjudicate
STRING_VALUE_TYPESautonumber(computed)select/radiopassword/secretare strings too — filtering a secret by substring is a security posture question, out of this cardmultiselect/checkboxes/tagslookup/master_detail/user/treeformulaNUMERIC·BOOLEAN·date·datetime·time{n: 5}cellSTRUCTURED_JSON_TYPESFILE_REFERENCE_TYPESOptions × real cost
NUMERIC∪BOOLEAN∪CALENDAR_DATE∪INSTANT∪CLOCK_TIME∪STRUCTURED_JSON, all existing sets)door-refusalrow family in spec, one door inobjectql/src/engine.tsbesideINVALID_FIELD; zero new vocabularyformulastays undecided until its return type is readable at the seamSTRING_VALUE_TYPES ∪ {autonumber}selectcodes, lookup ids, tags that WORK today — needs a measured census of real emitters (objectui filter builder, apps) before it could be adopted; not measurable from this repo alone$containsover a number answers[]with no signal (the "silent wrong answer" the conformance file header names); zero mechanism added四维分析(从业务角度)
一句话问题:客户的报表筛选器对「金额」列写了「包含 5」—— 平台是该悄悄返回空表,还是当场告诉他「金额不是文本,这个条件永远匹配不到」?
$searchFields门口已拒绝非文本字段。本卡决定的只是「写错的条件」答什么。⛔ 未测:objectui 的筛选器构造器与客户应用是否会发出这种条件 —— 那是本仓看不见的读数。field-value.zod.ts已声明的值域类,不新增公开词表;它与 comparand 门、$searchFields门同构 —— 平台已在两个相邻缝上跑这个形状。代价是多一道门(机制),不是多一套词表。D 零机制,但把「声明 ≠ 执行」留在一个已知格子上。[]零信号;C-deny 之下得到 400 + 字段声明类型,错误在写的那一刻被点名。推荐
D 先行(A 独立成立,#14079 落地即完整),C-deny 作为记录在案的形状,出现真实拉动即重开 —— 依据分歧推荐序:②零拉动 ⇒ 荐不扩散;①长远看 C-deny 是对的形状(收窄、复用已声明类、同构于既有门),因此回退项就是 C-deny 而不是 C-allow;③是唯一逆向的一轴,它压不过①②④合计。重开触发写在关单评论:某个真实生产者(objectui 筛选器、客户应用、或一次支持工单)对非文本声明字段发出文本算子。
若维护者裁 C-deny:两车道执行 ——
domain:spec在 comparand 表或同形姊妹表声明door-refusal行(引用既有集合,⛔ 不新铸集合);domain:engine在objectql/src/engine.ts的INVALID_FIELD门旁实现;A 的行留在门下;formula单列为「返回类型可读时再判」。置信缺口:objectui / 客户应用是否发出此类条件未测;
formula字段的声明返回类型在引擎缝上是否可读未测;MySQL、turso remote、真 mongod 三面(A 的)仍只有源码读数。os-decision-facets
field-value.zod.ts既有值域类、与 comparand 门/$searchFields门同构 ⇒ 若建门则 C-deny;但 A 已使各面同答一行,余下缺口只是「写错的条件静默为空」。[]零信号。formula返回类型在引擎缝可读性未测;MySQL · turso remote · 真 mongod 三面未实测。