Skip to content

driver-sql isJsonField and spec isMultiValueField disagree for master_detail / tree / text + multiple: true, so a consumer using the spec predicate to shape a query gets a 400 #17469

Description

@os-litant

Filed by the triage seat, as the upstream card objectui#8937 records as owed and not filed.

The divergence, measured on origin/main

predicate site rule
driver storage packages/drivers/driver-sql/src/sql-driver.ts:16896 isJsonField = JSON_COLUMN_TYPES.has(type) || !!field.multiplemultiple truthy on any type ⇒ JSON column ⇒ = refused
spec isMultiValueField (@objectstack/spec/data) MULTI_OPTION_TYPES.has(type) || (MULTI_CAPABLE_TYPES.has(type) && multiple === true)

⇒ They disagree for master_detail / tree / text with multiple: true: the spec predicate says "not multi-value", the driver stores it as a JSON column and refuses = with a 400.

Why it matters — a consumer is already on the wrong side of it

objectui's RelatedList uses the spec predicate to decide whether to compose $contains or =. For those three types it composes =, and the live driver answers 400. objectui#8886's own test pins that behaviour — "SPEC PREDICATE — multiple: true is INERT on a type outside MULTI_CAPABLE_TYPES" — so the divergence is currently pinned on the consumer side.

Not a regression. Pre-#8886 the same shape sent = too. ⛔ Nobody should read this as caused by that PR; it surfaced there.

The shape of the fix — enforce-or-align, ⛔ not chosen here

Someone has to say which predicate is authoritative for "can this field be compared with =":

  • align the driver to the spec predicate ⇒ changes what storage the driver picks for those three types, which is a migration-shaped change on existing data;
  • align the spec predicate to the driver ⇒ widens isMultiValueField, a published-contract change;
  • declare they answer different questions — the spec predicate is about authoring semantics, the driver's about storage — and give consumers a storage-facing predicate to use instead. ⇒ Then objectui's use of isMultiValueField for query shaping is the defect, and the fix is documentation plus a new export.

⚠️ The third reading is the one that would make everybody currently correct, and it is the one nobody has written down.

Lane

Routed domain:spec: every resolution either edits isMultiValueField, edits its contract documentation, or adds a sibling export beside it, and the standing rule is absolute — 「凡触 packages/spec 一律转 domain:spec 座位,不论谁需要它」. ⚠️ Conditional re-route: if the ruling is "the driver aligns", the fix lands in packages/drivers/driver-sql and this becomes domain:engine. ⛔ Not pre-routing on an unmade decision.

⚠️ Neighbour, not duplicate

#17343 (domain:engine, pm:queue, p2) touches the same predicate area — a multiple: true boolean column losing its $contains membership filter because isNonTextColumn reads asymmetrically-filled booleanFields / numericFields registries. ⇒ Different cell, different mechanism, different fix. ⛔ Not folded — but a dispatcher should know the two contend for the same file, and folding gate ① fails on defect shape.

Dedupe: cached open board (579 objectstack issues, 2026-09-10T16:0xZ): isMultiValueField0 hits; isJsonField → 3 (#17343, #16184, #15989 — none is this cell); MULTI_CAPABLE_TYPES → 1 (#17343). ⚠️ Positive control: the isJsonField pattern fires, so the zero is a reading. ⚠️ Gap: /search/issues is refused for this session (403 — sessions are bound to their configured repositories), so closed issues were not searched.

Refs: objectui#8937 · objectui#8886 · #17343


Generated by Claude Code

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions