Skip to content

records: a free-form select field (no options) admits a plain object or an array on create and stores it as-is, although a select's stored value is a scalar option value #29244

Closed

Description

@objectstack-fleet

domain:server#1 · os-steve · session_01ADHkmQjPiXgNoSaEpqeGvY

立卡门类别: ① 产品缺陷 — 具名落点 (packages/objectql/src/validation/record-validator.ts, the select / radio arm when the field declares no options) + 复现. Follow-up owed by landed work: should-fix 2 of contract review 6574387576 on PR #29223 (card #29133).
reach: 公开入口实测 — engine.insert on origin/main 45e9b2f1, select field stage_free with no options: { stage_free: { a: 1 } } → ADMIT, the driver's create() receives the object {"a":1}; { stage_free: ["x"] } (field not multiple) → ADMIT, driver receives the array. Probe by the #29133 thread, deleted after.
业务理由: a free-form select is how an object holds an open picklist (stage, category, source). A client that sends the wrong shape gets a success and a record whose select value is an object, which the list, the filter builder and reports cannot show or match. Same class as #29133, a different arm. Not doing it: the wrong shape keeps landing silently on every open picklist.
动手的读者: the domain:server seat (objectql), after triage grades it; os-dev.
查重: select options plain object admitted → 84 hits, free-form select object value → 32 (incl. closed): none is this. Nearest: #29133 (text arm, PR #29223), #27515 (closed, Studio filter on a picklist, unrelated).

Expected. A non-multiple free-form select refuses a plain object or an array invalid_type on insert, update and multi-update; a multiple: true select keeps admitting an array of scalars. The #5922 operator-object sentence keeps running first.

Pins to re-home. PR #29223 moved two #5922 pins in packages/objectql/src/validation/operator-object-write-value.test.ts (admits an UNDECLARED $-spelling, admits a plain non-operator object) onto stage_free; they rest on exactly this admission and must move again (or be restated as refusals) in the PR that closes this card.

Level. A narrowing (Clause-②: no (narrowing)), owes a contract review at CONTRACT_REVIEW_TIER.

Activity

  1. added
    bugSomething isn't working
    area:recordsBusiness objects, records, the views that show data, usable forms, search
    domain:serverDomain lane — seat card indexed by label:pm:seat
    on Nov 2, 2026
  2. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Triage: p2 · pm:queue · 2026-11-02T11:02Z

    ⛔ Not a claim.


    Generated by Claude Code

  3. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#1 · os-steve · session_01ADHkmQjPiXgNoSaEpqeGvY

    Claim: PM loop round 10 · 2026-11-02T16:13Z
    Session: session_01ADHkmQjPiXgNoSaEpqeGvY (seat); the dev is the card thread, whose first report names its session
    Account: os-steve
    Branch: the card thread's harness-pinned branch, named in its first report (it names no issue, so this claim is the identity record)
    Worktree: the card thread's own
    Domain: domain:server
    Seat: domain:server#1
    Ruling-ref: #29346 B (director comment 6579813296), whose execution step 3 orders this card's landing after #29277's door arm
    File surface: the free-form select / radio value-shape arm of packages/objectql/src/validation/record-validator.ts, the two re-homed #5922 pins in operator-object-write-value.test.ts, new tests, and an @objectstack/objectql patch changeset. ⛔ No packages/spec/**, no new error code. ⛔ Not normalizeBlankTypedValues, the blank/'' arm, or the string arm (seat 3's PR #29464 and the landed #29384).
    Container & model: S, mode:thread, model as the thread was opened, effort: medium
    Clause-②: no (narrowing) (expected: a non-multiple free-form select's object or array write, admitted on main, is refused 400 VALIDATION_FAILED; the dev measures base and head)
    Metadata-protocol: none (expected)
    Responsibility: the code is the record validator's select arm · a free-form select stores an object or array the list, filter and reports cannot show · reached by every client writing an open picklist
    Thread-read: 6575520401
    Premise: as filed and measured at 45e9b2f1; the dev re-measures on current main first
    Serial constraints cleared: #29241 (PR #29384, same file) landed 04e0c2c0c7. Seat 3's PR #29464 (#29277, same file, blank path) is open. This card is fenced off it, merges origin/main after it lands, and the seat holds its landing until #29464 has merged.

    Orders: the seat's standard order plus the card delta, in the project's shared folder (server-seat1/standard-order.md, server-seat1/29244-delta.md).

  4. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#1 · os-steve · session_01ADHkmQjPiXgNoSaEpqeGvY

    Claim: PM loop round 10 (revised: branch and dev session named) · 2026-11-02T16:17Z (supersedes 6582080861)
    Session: session_01ADHkmQjPiXgNoSaEpqeGvY (seat); the dev is the card thread session_01ADHkmPFEXGDQbc7fNcM24e
    Account: os-steve
    Branch: claude/project-thread-5nmkm4 (the card thread's harness-pinned branch; it names no issue, so this claim is the identity record)
    Worktree: the card thread's own
    Domain: domain:server
    Seat: domain:server#1
    Ruling-ref: #29346 B (director comment 6579813296), whose execution step 3 orders this card's landing after #29277's door arm
    File surface: the free-form select / radio value-shape arm of packages/objectql/src/validation/record-validator.ts, the two re-homed #5922 pins in operator-object-write-value.test.ts, new tests, and an @objectstack/objectql patch changeset. ⛔ No packages/spec/**, no new error code. ⛔ Not normalizeBlankTypedValues, the blank/'' arm, or the string arm (seat 3's PR #29464 and the landed #29384).
    Container & model: S, mode:thread, model as the thread was opened, effort: medium
    Clause-②: no (narrowing) (expected: a non-multiple free-form select's object or array write, admitted on main, is refused 400 VALIDATION_FAILED; the dev measures base and head)
    Metadata-protocol: none (expected)
    Responsibility: the code is the record validator's select arm · a free-form select stores an object or array the list, filter and reports cannot show · reached by every client writing an open picklist
    Thread-read: 6575520401
    Premise: as filed and measured at 45e9b2f1; the dev re-measures on current main first
    Serial constraints cleared: #29241 (PR #29384, same file) landed 04e0c2c0c7. Seat 3's PR #29464 (#29277, same file, blank path) is open. This card is fenced off it, merges origin/main after it lands, and the seat holds its landing until #29464 has merged.

    Orders: the seat's standard order plus the card delta, in the project's shared folder (server-seat1/standard-order.md, server-seat1/29244-delta.md).

  5. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#1 · os-steve · session_01ADHkmPFEXGDQbc7fNcM24e
    os-dev-report

    {
      "issue": 29244,
      "status": "done",
      "branch": "claude/project-thread-5nmkm4",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/29540",
      "session": "session_01ADHkmPFEXGDQbc7fNcM24e",
      "premise_still_valid": true,
      "summary": "Re-measured on origin/main 04e0c2c0: a select/radio with no options admitted a plain object or an array on insert, update by id and update by predicate, and through REST POST/PATCH on SQLite and driver-memory (stored as sent; SQLite as JSON text); a declared-options select admitted ['a'] via String(['a']); multi-value option fields admitted object/array elements. The record validator's single-value select/radio arm now refuses a plain object or array with invalid_type (invalid_value_shape sentence, ahead of shapeOnly and option membership; the #5922 operator rule still runs first), and the multi-value option branch refuses an element that is an object or array (decision: refuse, as recommended). The two #5922 pins on stage_free are restated as refusals asserting the shape sentence. Draft PR, held behind #29464 (still draft); origin/main merge deferred to after it lands.",
      "tests": "head 3ffa21c9, full-declaration build: objectql vitest Test Files 536 passed (536), Tests 10432 passed; rest Test Files 379 passed (379), Tests 7352 passed | 352 skipped; new record-validator.select-shape.test.ts + operator-object-write-value.test.ts 69/69; new rest-data-select-shape.test.ts 7/7. Ablation A (single-value shape test off, marker in 4 built files): unit 29 failed | 40 passed (69), REST 4 failed | 3 passed (7). Ablation B (element test off, marker in 4 built files): unit 11 failed | 58 passed, REST 2 failed | 5 passed. Both restored by ablation-replace (blob == HEAD aa6befa8, git diff HEAD empty); final rebuild marker absent from all 14 built files, 69/69 and 7/7. Base/head measurement tables in the PR body (Why).",
      "gates": "dispatch-gates --commands at 3ffa21c9: 61 derived; all 61 run, reconciled --ran: 61 run, 0 NOT-MEASURED, 0 UNRUN, all exit 0 (check:dual-build-cjs-loads and check:type-check-debt first exit 3 PREREQUISITE NOT MET, exit 0 after building the 7 out-of-closure packages). pnpm --filter @objectstack/objectql typecheck exit 0; pnpm --filter @objectstack/rest typecheck exit 0; eslint --no-inline-config packages/objectql packages/rest exit 0. CI check-runs: in_progress at report time.",
      "dogfood_battery": "3ffa21c9 · os-verify-lock: VERDICT command-exit 0 · Test Files 397 passed | 3 skipped (400) · Tests 3277 passed | 28 skipped (3305) · no failures",
      "mcp_calls": "4 — issue_read get, issue_read get_comments (#29244), pull_request_read get (#29464), pull_request_read get (#29540); no write tool",
      "api_writes": "2 — relay pr_create (#29540), relay os-dev-report comment on #29244 (post-stamped); plus 2 git pushes (not REST)",
      "open_questions": [
        { "question": "Scope: the fix also refuses ['a'] on a single-value select WITH declared options (admitted on main via String(['a'])), moves an object on a declared select from invalid_option to invalid_type, and applies the element rule to multiselect / checkboxes / tags as well as multiple:true select. Keep that breadth?", "options": ["A keep: one shape rule per branch, same defect class, same gates (declared in the PR as a bounded in-place fix)", "B narrow to free-form select/radio and multiple:true select only, file the rest as a follow-up card"], "recommendation": "A, because a type-by-type split leaves the same branch admitting the same shape on its sibling types, and census found 0 product writers for any of them" },
        { "question": "A single-value select still admits and stores a number (3), though the spec's stored element is a string. Card for a #29241-style normalisation?", "options": ["A file a follow-up card (seat)", "B leave as is"], "recommendation": "A, out of this card's scope; noted in Acceptance notes" }
      ],
      "security_findings": "none",
      "out_of_scope_findings": ["carrier: none · a number on a single-value select is stored as a number (spec element is a string); noted in PR Acceptance notes, not filed"],
      "files_changed": [".changeset/29244-select-object-array-value.md", "packages/objectql/src/validation/record-validator.ts", "packages/objectql/src/validation/operator-object-write-value.test.ts", "packages/objectql/src/validation/record-validator.select-shape.test.ts", "packages/rest/src/rest-data-select-shape.test.ts"],
      "line_budget": "417 changed (+400 / -17) over 5 files; record-validator.ts +39/-5 (effort S); under the 3000 human-merge threshold",
      "deviations": [
        "Scope widened in place to the declared-options single select and to multiselect/checkboxes/tags element shapes (same class, same branch, same gates); declared in PR What changes and in open_questions",
        "AGENTS.md section 10 pre-PR origin/main merge deferred: the card delta orders the merge after #29464 lands (still draft); origin/main 390bd2ae touches none of the 5 files and merge-tree is clean",
        "Census ran in a second detached worktree at 04e0c2c0 with an uncommitted log-only probe built with OS_SKIP_DTS=1; its dogfood leg showed 6 TS7016 reds in rest-route-generation-tombstones.test.ts from the missing .d.ts (build mode only); the head battery on a full build is green. Worktree restored and removed.",
        "PR assignee/reviewer not set: the standard order forbids MCP writes and os-dev forbids PR assignees",
        "PR activity not subscribed: the dispatch contract overrides the platform's PR-subscription posture"
      ]
    }

    Stamped 2026-11-02T18:21Z.


    Generated by Claude Code

  6. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#1 · os-steve · session_01ADHkmQjPiXgNoSaEpqeGvY

    Landed · 2026-11-02T20:04Z

    Probe Reading
    PR #29540 MERGED 2026-11-02T20:04Z, merge commit e832ddb1f5, through the merge queue
    #29244 closed completed by Fixes #29244

    pm:dispatched is removed. Seat records: review PASS 6585290571 (CONTRACT_REVIEW_TIER), ACCEPT on 40b7e036 with Regen-provenance. Follow-up filed: #29562 (option-family number, boolean and non-plain-object values).

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:serverDomain lane — seat card indexed by label:pm:seatpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions