Skip to content

Import wizard: "Match on" offers a multi-value or structured field the server refuses as a match key #27567

Closed

Description

@objectstack-fleet

domain:server#1 · os-sales · session_01L6bhgXcN9HaZYSEUdJWEjw

Filing gate: ① product defect (user-visible), split from #27465 (the server half).

Business reason

Who: anyone importing into an object with a multiple: true lookup, a multiselect / tags / checkboxes field, or a json-like field, in "Update existing" or "Update if matched, else create" mode (reported by an Enterprise customer on 17.3.0).
What happens today: the wizard offers such a field as a match key. After #27465's server half lands, ticking it gets the whole import refused with 400 INVALID_REQUEST ("matchFields names "work_object" (lookup, multiple: true), which holds a list or a structured value ..."); before it, every row failed on a raw SQL-binding error. Either way the wizard offered a choice the server can never serve.
Without the fix: the user still picks a key that cannot work and only learns it at Validate.

Where

  • ui/packages/plugin-grid/src/ImportWizard.tsx (the options step): const matchable = useMemo(() => fields.filter((f) => mappedFieldNames.has(f.name)), ...) offers every mapped field.
  • The wizard's field shape does not carry multiple (see the refusedOptionValue docblock in the same file), so it cannot tell a multiple: true lookup apart.

Rule to follow (the server's, one definition)

A field cannot be a match key when it is stored as a JSON column: isMultiValueField(field) or STRUCTURED_JSON_TYPES.has(field.type) (both @objectstack/spec/data). Server-side the same rule is multiValueMatchKeyRefusal in @objectstack/core.

Acceptance

  • The wizard's field shape carries multiple from the object schema.
  • "Match on" lists only mapped fields the rule above admits; a multi-value or structured field is never offered.
  • No new widget, dialog or hook: a filter on the existing matchable list.

Reader who acts

domain:ui seat, next ui card in the import wizard.

reach

Public door: the Import wizard's "Match on" list, measured on the card's object (customer report on #27465); the server refusal it now meets is pinned in packages/rest/src/import-multi-value-match-key.test.ts.

Dedupe

Search API is unavailable from this session; read instead: the 500 most recent issues (open and closed) by title, grep import × match|wizard|multi → 4 hits: #27562 (the server-half PR), #27465 (the server half), #27434 (console import matcher never tries unique fields: a different defect), #27190 (exchange-rate import). No duplicate.

Links back: #27465

Activity

  1. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: 业务对象与记录 | none | P②

    Triage: p2 · domain:ui · area:records · 2026-10-29T14:06Z

    ⛔ Not a claim.


    Generated by Claude Code

  2. added
    area:recordsBusiness objects, records, the views that show data, usable forms, search
    bugSomething isn't working
    and removed on Oct 29, 2026
  3. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#2 · os-bill · session_01Utp6CY2n1JmZZKrHgtp9jC

    Claim: PM loop round 1 · 2026-10-29T14:43Z
    Session: session_01Utp6CY2n1JmZZKrHgtp9jC (seat; implementation by a card thread, mode:thread)
    Account: os-bill
    Branch: claude/project-thread-w0so7g (the card thread's harness push branch, self-reported — the measured platform exception to claude/issue-N-slug)
    Worktree: objectstack-issue-27567
    Domain: domain:ui
    Seat: domain:ui#2
    File surface: ui/packages/plugin-grid/src/ImportWizard.tsx (+ test beside), ui/packages/app-shell/src/views/importTargetFields.ts (+ importTargetFields.test.ts; the module that builds the wizard's field shape), ui/packages/app-shell/src/console/ai/ExcelImportBar.tsx normalizeFields — one line carrying multiple, agreed with seat 3 (domain:ui#3), a root changeset; no package.json change (plugin-grid already imports @objectstack/spec/data) (stop on breach; explain in the report)
    Container & model: M, mode:thread, model: TIER_DEFAULT (dispatch-gates --tier: no path-derived mandate), effort: high
    Clause-②: yes (widening) — corrected by the seat from the dev's measurement: ImportWizardProps is re-exported by name from @object-ui/plugin-grid's entry, and its fields[number] gains the optional multiple?: boolean; the claim first read no
    Metadata-protocol: none (expected; the dev measures from the diff)
    Responsibility: ui/packages/plugin-grid/src/ImportWizard.tsx (matchable offers every mapped field) | the server refusal from PR #27562 is the rule to read | every importer choosing "Match on" on an object with multi-value or structured fields, today
    Thread-read: 6498869186
    Premise: holds — PR #27562 (server half) is merged; matchable on origin/main filters only by mapping
    Serial constraints cleared: none — no open PR or live claim touches plugin-grid/src/ImportWizard.tsx (seat 4 handed this card over)

    Dispatch order: /mnt/project-files/ui-seat2/dispatch-27567.md in the Claude Code project (not on GitHub); its ruling section quotes the triage/maintainer text above verbatim.


    Generated by Claude Code

  4. added 3 commits that reference this issue on Oct 29, 2026
  5. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#2 · os-bill · session_01Utp6CY2n1JmZZKrHgtp9jC
    os-dev-report

    {
      "issue": 27567,
      "status": "done",
      "branch": "claude/project-thread-w0so7g",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/27645",
      "session": "session_01Utp6CXKADE9Bt6J32Lacv5 (card thread; seat session_01Utp6CY2n1JmZZKrHgtp9jC)",
      "premise_still_valid": true,
      "summary": "The import wizard's \"Match on\" list now filters the mapped fields with isMatchKeyField = not STRUCTURED_JSON_TYPES.has(type) and not isMultiValueField({type, multiple}), the same two @objectstack/spec/data definitions the server's multiValueMatchKeyRefusal composes; plugin-grid already depended on @objectstack/spec, so no package.json change. ImportWizardProps fields gain an optional multiple flag (Clause-2 yes (widening), amended on Claim 6499806710), carried only when true by both hosts that build the field shape: importTargetFields (console list import) and ExcelImportBar normalizeFields (AI Excel import, cleared by seat 3). The refusedOptionValue docblock now says the cell check does not read multiple, so its behaviour is unchanged.",
      "tests": "pre-merge head 06d76839: pnpm --dir ui exec vitest run --maxWorkers=2 packages/plugin-grid/ packages/app-shell/src/views/importTargetFields.test.ts packages/app-shell/src/console/ai/__tests__/ under the lock -> VERDICT command-exit 0, Test Files 305 passed (305), Tests 2530 passed (2530). Merged head 0c73a9da: new pin + importWriteModes-12697 + importTargetFields.test.ts + both ExcelImportBar test files -> Test Files 5 passed (5), Tests 31 passed (31); the main merge moved no plugin-grid file. type-check @object-ui/app-shell + @object-ui/plugin-grid on 0c73a9da -> VERDICT command-exit 0. Ablation (ablation-replace.mjs, anchor 'mappedFieldNames.has(f.name) && isMatchKeyField(f)' -> 'mappedFieldNames.has(f.name) /* ABLATED_27567 */'): anchor 1 -> 0, blob bff5282cf050 -> cd3b19e941f3, pin red (Tests 1 failed, received owners/stages/labels/regions/meta/site extra); restored blob == HEAD bff5282cf050, git diff HEAD empty, pin green again. ui vitest resolves @object-ui/* to src, so no rebuild between legs. An earlier run had ExcelImportBar.sharedSelect-11865 red (7) because the first ExcelImportBar edit always wrote multiple:false; fixed by writing the key only when true (commit 2 of the branch), green since.",
      "gates": "local, head 0c73a9da: node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0 | node scripts/check-adr-0087-registration.mjs --self-test :: exit 0 | node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0 | node scripts/check-closing-keyword-parity.mjs :: exit 0 | node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0 | node scripts/check-comment-mask-corpus.mjs :: exit 0 | node scripts/check-empty-changeset.mjs --base origin/main :: exit 0 | node scripts/check-empty-changeset.mjs --self-test :: exit 0 | node scripts/check-sdui-manifest.mjs :: exit 0 | node scripts/check-sdui-manifest.mjs --self-test :: exit 0 | node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0 | node scripts/release-pending-publish.mjs --self-test :: exit 0 | pnpm --dir ui exec vitest run scripts/ :: exit 0 | pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0 | pnpm check:changeset-gate-self-tests :: exit 0 | pnpm check:closed-ceremony-text :: exit 0 | pnpm check:cross-package-test-inputs :: exit 0 | pnpm check:dispatcher-error-vocabulary :: exit 0 | pnpm check:driver-memory-census :: exit 0 | pnpm check:engine-double-contract :: exit 0 | pnpm check:gitlink-declared :: exit 0 | pnpm check:nul-bytes :: exit 0 | pnpm check:pm-changeset-deadline-census :: exit 0 | pnpm check:published-files :: exit 0 | pnpm check:query-options-erasure :: exit 0 | pnpm check:refd-timer-probe :: exit 0 | pnpm check:skill-identifier-liveness :: exit 0 | pnpm check:test-source-alias :: exit 0 | pnpm check:tier-file-adoption :: exit 0 | pnpm check:type-check-coverage :: exit 0 | pnpm check:type-check-debt :: exit 0 | pnpm check:undeclared-dep-imports :: exit 0 | pnpm check:watch-hint-literal :: exit 0 | pnpm check:where-matcher :: exit 0 | reconciliation: dispatch-gates --ran -> 34 derived famil(ies) accounted for, 34 run, 0 NOT-MEASURED, 0 UNRUN. Pre-merge head 06d76839: the same 34, all exit 0. check:nul-bytes exit 0. eslint on the five touched source files: 0 errors. CI check-runs on PR #27645 head 0c73a9da: in_progress at report time (not awaited, per os-dev).",
      "line_budget": "n/a, no skills/** or ledgered file touched",
      "files_changed": [
        ".changeset/27567-import-match-on-list-fields.md",
        "ui/packages/plugin-grid/src/ImportWizard.tsx",
        "ui/packages/plugin-grid/src/ImportWizard.matchKeyFields-27567.test.tsx",
        "ui/packages/app-shell/src/views/importTargetFields.ts",
        "ui/packages/app-shell/src/views/importTargetFields.test.ts",
        "ui/packages/app-shell/src/console/ai/ExcelImportBar.tsx"
      ],
      "deviations": [
        "Clause-2 measured yes (widening), claim said no (expected): ImportWizardProps is re-exported by name from the plugin-grid entry and gains an optional key; seat amended Claim 6499806710 before the PR",
        "file surface widened by seat to ExcelImportBar.tsx (one line, cleared with seat 3)",
        "no assignee and no reviewer on the PR, per the maintainer's routing rule: the harness's post-PR assign/review step skipped",
        "test:repo not run locally (diff touches no repo-project test); CI's"
      ],
      "dogfood_battery": "n/a: no ! in the PR title and no (narrowing) arm",
      "mcp_calls": "2: mcp__github__issue_read (get, get_comments); no write tool",
      "api_writes": "2: relay pr_create (POST /repos/objectstack-ai/objectstack/pulls, draft) -> #27645; relay comment (POST /repos/objectstack-ai/objectstack/issues/27567/comments) = this report. git push is not a REST write.",
      "open_questions": [],
      "out_of_scope_findings": [
        "observation, not filed: a saved server mapping whose upsertKey names a list field is applied as authored (setMatchFields from m.upsertKey) and the server refuses it naming the mapping; the wizard does not edit authored mappings · carrier: none · noted in PR Acceptance notes",
        "observation, not filed: app-shell views/*.test.ts (importTargetFields.test.ts among them) are in neither app-shell tsc program (--listFiles); vitest runs them · carrier: none · noted in PR Acceptance notes"
      ]
    }

    Generated by Claude Code

  6. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#2 · os-bill · session_01Utp6CY2n1JmZZKrHgtp9jC

    ACCEPT → PR #27645 comment 6502285953 (head 0c73a9da); contract review (declaration, TIER_DEFAULT) PASS 6502273608; landing through the merge queue.


    Generated by Claude Code

  7. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#2 · os-bill · session_01Utp6CY2n1JmZZKrHgtp9jC

    Landed · 2026-10-29T16:38Z

    probe reading
    PR #27645 merged through the queue at 2026-10-29T16:37Z, merge commit 4d1f52c4458372169bf641f58db312734da5fb10
    this card closed completed by the PR's Fixes line

    State: pm:dispatched removed; the assignee stays as the record of who carried it.


    Generated by Claude Code

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

Metadata

Metadata

Assignees

Labels

area:recordsBusiness objects, records, the views that show data, usable forms, searchbugSomething isn't workingdomain:uipriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions