Skip to content

fix(objectql): enforce array shape for multi-value fields in the write pipeline - #2553

Merged
os-zhuang merged 1 commit into
mainfrom
fix/objectql-multivalue-shape-2552
Jul 4, 2026
Merged

fix(objectql): enforce array shape for multi-value fields in the write pipeline#2553
os-zhuang merged 1 commit into
mainfrom
fix/objectql-multivalue-shape-2552

Conversation

@baozhoutao

Copy link
Copy Markdown
Contributor

Closes#2552

Problem (scanned from source, verified via REST e2e)

The write pipeline stored a lone scalar sent at a multi-value field verbatimPATCH { labels: "frontend" } at a multiselect returned 200 and the column read back as a string, silently corrupting the array shape for every consumer. Found via the console bulk-edit dialog (pre-#2186 objectui sent scalars for multi params), but the server-side hole accepts it from ANY client.

Worse, select+multiple mis-rejected legal arrays: the single-select validation branch (record-validator.ts) stringified ["a","b"] to "a,b" and threw invalid_option.

Scan also confirmed the runtime expands Field.user to type: 'user' (NOT 'lookup') — e2e against the showcase team_members field caught that a lookup-only type set missed it. Per packages/spec/src/data/field.zod.ts:377, multiple applies to select/lookup/file/image, and user is stored identically to lookup.

Fix (@objectstack/objectql)

  • normalizeMultiValueFields (new, called at all 4 validateRecord call-sites in engine.ts): coerces unambiguous scalars (string/number/boolean) into single-element arrays IN PLACE before validation, for multiselect/checkboxes/tags and select/radio/lookup/user/file/image with multiple: true.
  • validateRecord: multi-value fields must now be arrays — anything un-wrappable (plain objects, junk) is rejected with a new invalid_type code instead of hitting storage. Array elements are still validated against options; reference/attachment types skip options (integrity handled elsewhere).
  • Single select/radio branch now guarded with multiple !== true, fixing the legal-array mis-rejection.

Tests

  • Unit: record-validator.test.ts — normalization, shape rejection, element option checks, single-select non-regression (incl. user/file types).
  • Engine-level: engine-multivalue-normalize.test.ts (new) — asserts on what the driver receives across insert/update for all covered types.
  • Full @objectstack/objectql suite: 59 files / 737 tests pass.
  • REST e2e on a live showcase dev server (SQLite inspected directly):
    • scalar PATCH {"team_members":"<uid>"} at a user+multiple field → stored as ["<uid>"]
    • junk object → 400 invalid_type
    • illegal option scalar → 400 invalid_option
    • legal arrays pass through unchanged ✅

🤖 Generated with Claude Code

Co-Authored-By: Claude noreply@anthropic.com

@vercel

vercelBot commented Jul 4, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

ProjectDeploymentActionsUpdated (UTC)
specReadyReadyPreview, CommentJul 4, 2026 1:42am

Request Review

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation tests tooling size/m labels Jul 4, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/objectql.

14 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/concepts/core/services.mdx(via @objectstack/objectql)
  • content/docs/concepts/implementation-status.mdx(via @objectstack/objectql)
  • content/docs/concepts/metadata-lifecycle.mdx(via @objectstack/objectql)
  • content/docs/concepts/packages.mdx(via @objectstack/objectql)
  • content/docs/guides/authentication.mdx(via @objectstack/objectql)
  • content/docs/guides/deployment-vercel.mdx(via @objectstack/objectql)
  • content/docs/guides/formula.mdx(via packages/objectql)
  • content/docs/guides/kernel-services.mdx(via @objectstack/objectql)
  • content/docs/guides/objectql-migration.mdx(via @objectstack/objectql)
  • content/docs/guides/packages.mdx(via @objectstack/objectql)
  • content/docs/guides/plugins.mdx(via @objectstack/objectql)
  • content/docs/protocol/objectos/index.mdx(via @objectstack/objectql)
  • content/docs/protocol/objectql/state-machine.mdx(via @objectstack/objectql)
  • content/docs/releases/v9.mdx(via @objectstack/objectql)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@os-zhuang
os-zhuang merged commit 24b62ee into mainJul 4, 2026
16 checks passed
@os-zhuang
os-zhuang deleted the fix/objectql-multivalue-shape-2552 branch July 4, 2026 02:52
…e pipeline (#2552)
A lone scalar sent at a multiselect / checkboxes / tags / select+multiple /
lookup+multiple field used to pass validation and reach the driver verbatim
(PATCH {"labels":"frontend"} → 200, stored as a string), silently corrupting
the column shape for every array-consumer. select+multiple was worse: a legal
ARRAY was stringified to "a,b" and mis-rejected as invalid_option, while
lookup+multiple had no shape check at all.
- normalizeMultiValueFields(): wrap unambiguous scalars (string/number/boolean)
into single-element arrays, in place, before validation — called at all four
validateRecord sites in engine insert/update (covers REST, engine callers,
and SeedLoader, which all funnel through them)
- validateOne(): multi-value fields must be arrays; anything un-wrappable
(plain objects, nested junk) is rejected with a new `invalid_type` code
instead of hitting storage
- select/radio + multiple:true now routes through the multi-value branch with
per-element option validation
Closes#2552
Co-Authored-By: Claude <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

objectql: write pipeline accepts scalar values for multi-value fields (multiselect/tags/select+multiple/lookup+multiple), corrupting stored shape

2 participants

@baozhoutao@os-zhuang