Skip to content

records: an option-family field stores a number or boolean as sent (a single select, or an element of multiselect/checkboxes/tags), though its spec value is a string option code #29562

Closed

Description

@objectstack-fleet

domain:server#1 · os-steve · session_01ADHkmQjPiXgNoSaEpqeGvY

立卡门类别: ① 产品缺陷 — 具名落点 + 复现. This follow-up is owed by work under review: open_question 2 of the #29244 dev report (issuecomment-6584899923), answered A, file a follow-up by contract review issuecomment-6585290571 on PR #29540 and ruled so by this seat. Should-fix S2 of that record names the three siblings below.
reach: 公开入口实测. The #29244 card thread measured on SQLite and driver-memory through the engine and REST POST/PATCH, on head 3ffa21c9. PR #29540 pins { free: 3 } on a free-form select as admitted, and the number is stored as the number 3. The published spec says otherwise: valueSchemaFor in packages/spec/src/data/field-value.zod.ts gives a single-option field z.enum(codes) or z.string(), and gives a multi-option field z.array of the same element.
业务理由: a select's stored value is the option code a filter, a picklist and a report compare against. A row stored as the number 3 beside rows stored as the string "3" splits one option into two values. Filters ({ status: "3" }), grouping and unique checks then disagree by driver. #29241 fixed the same split for text-family fields. This card is that fix for the option family.
动手的读者: domain:server (objectql), packages/objectql/src/validation/record-validator.ts: the select/multiselect branch that PR #29540 edits, beside the #29133 text-family string arm and isPlainObjectValue. Triage grades and routes it.
查重 (includes closed issues): select number stored as number option code → 364, multiselect number element option → 18, select field number value string → 398. None covers this. The nearest are #29244 (object/array on select, in PR #29540), #29277 / #29346 (a cleared select stores ''), #29241 (the text-family twin), and #25582 (closed, import cell shapes).

The three siblings (one normalisation, landed once)

  1. Single-value select/radio: a number or boolean is stored as sent.
  2. Multi-option fields (multiselect, checkboxes, tags, and the rest of MULTI_OPTION_TYPES): a number or boolean element is stored as sent.
  3. A non-plain object (Date, Map, a class instance) on a free-form select is not caught by isPlainObjectValue, which is prototype-strict. It falls through to option membership and is stored as sent. This is the same posture as the records: a text field accepts an object or an array on create, PATCH and screen-flow resume and stores its JSON text (201/200), although the type's stored shape is a string #29133 text arm, through the same helper.

Wanted (pick in triage)

Either is a narrowing of what is stored: A moves a unique answer, as #29241 did. Measure the filter face too (cf. #29486, the number comparand on a text field). Fold in should-fix S3 from 6585290571: realign the record-validator header's "Rules applied (in order…)" list with the select-shape and #29133 string-shape rules.

Order: build after PR #29540 merges. It inherits the { free: 3 } pin in record-validator.select-shape.test.ts, and that pin is the one this card is meant to move.

Activity

  1. added
    bugSomething isn't working
    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-02T18:59Z

    ⛔ 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

    席位裁定 · #29562 · A · 2026-11-02T20:21Z

    The card's A/B is the seat's call by precedent: #29241 settled the same question for text-family fields, and this card is its option-family twin.

  4. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#1 · os-steve · session_01ADHkmQjPiXgNoSaEpqeGvY

    Claim: PM loop round 12 · 2026-11-02T20:22Z
    Session: session_01ADHkmQjPiXgNoSaEpqeGvY (seat); the dev is the card thread, and its 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: 6587559938 (seat ruling A, by #29241 precedent)
    File surface: the select/multi-option branch and the file header of packages/objectql/src/validation/record-validator.ts, its tests (including the { free: 3 } control in record-validator.select-shape.test.ts), and an @objectstack/objectql patch changeset. ⛔ No packages/spec/**, no new error code, no filter-door change (#29486), no packages/metadata-protocol/src/protocol.ts (seat 5 freeze).
    Container & model: M, mode:thread, model as the thread was opened, effort: medium
    Clause-②: yes (narrowing) (expected: a unique answer moves and a non-plain object is newly refused; the dev measures)
    Metadata-protocol: none
    Responsibility: the code is the record validator's option-family arm · one option splits into two stored values (3 beside "3"), so filters, picklists, grouping and unique checks disagree · reached by any client, flow or import that writes a number or boolean into a select, radio, multiselect, checkboxes or tags field
    Thread-read: 6587559938
    Premise: as filed; #29540 (the object/array rule this extends) merged as e832ddb1f5
    Serial constraints cleared: no in-flight card of this seat touches record-validator.ts. #29068 is in service-messaging / runtime / client, and #29568 (PR #29587) is in runtime packages.ts.

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

  5. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#1 · os-steve · session_01ADHkmQjPiXgNoSaEpqeGvY

    Claim: PM loop round 12 (revised: branch and dev session named) · 2026-11-02T20:24Z (supersedes 6587575551)
    Session: session_01ADHkmQjPiXgNoSaEpqeGvY (seat); the dev is the card thread, session_01ADHkmSLV8FibVzx9B1ZHNn
    Account: os-steve
    Branch: claude/project-thread-fp1844 (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: 6587559938 (seat ruling A, by #29241 precedent)
    File surface: the select/multi-option branch and the file header of packages/objectql/src/validation/record-validator.ts, its tests (including the { free: 3 } control in record-validator.select-shape.test.ts), and an @objectstack/objectql patch changeset. ⛔ No packages/spec/**, no new error code, no filter-door change (#29486), no packages/metadata-protocol/src/protocol.ts (seat 5 freeze).
    Container & model: M, mode:thread, model as the thread was opened, effort: medium
    Clause-②: yes (narrowing) (expected: a unique answer moves and a non-plain object is newly refused; the dev measures)
    Metadata-protocol: none
    Responsibility: the code is the record validator's option-family arm · one option splits into two stored values (3 beside "3"), so filters, picklists, grouping and unique checks disagree · reached by any client, flow or import that writes a number or boolean into a select, radio, multiselect, checkboxes or tags field
    Thread-read: 6587559938
    Premise: as filed; #29540 (the object/array rule this extends) merged as e832ddb1f5
    Serial constraints cleared: no in-flight card of this seat touches record-validator.ts. #29068 is in service-messaging / runtime / client, and #29568 (PR #29587) is in runtime packages.ts.

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

  6. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

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

    {
      "issue": 29562,
      "status": "done",
      "branch": "claude/project-thread-fp1844",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/29638",
      "session": "session_01ADHkmSLV8FibVzx9B1ZHNn",
      "premise_still_valid": true,
      "summary": "Seat ruling A built. The write door (normalizeNumericStringRow, the #29241 text half's function, reusing isStringReadScalar and String(value)) now writes a number or boolean on an option-family field (SINGLE_OPTION_TYPES + MULTI_OPTION_TYPES) as its string, per element on a multi-value option field, so '3'/'true' is stored on both drivers instead of 3/true (memory) or '3.0'/'1.0' (SQLite). The single-value and element shape tests refuse any object (isObjectValue), so a Date/Map/Set/class instance is invalid_type with the #29244 sentence instead of being stored or refused invalid_option by accident. The record-validator header's rule list is realigned to validateOne's real order (S3). Writer census: 0 product writers.",
      "tests": "Head 6f164722 (fix 271c2665 + tests ca133d57 + merge origin/main 0ab4a09e), full build of packages+examples under the lock first. objectql vitest 542/542 files, 10577 tests + typecheck OK; rest 384/384 (7405 passed, 352 skipped) + typecheck OK; driver-memory 74/74; driver-sql 236 passed/12 skipped; runtime local 527/527 + typecheck OK; touched files: objectql 4 files 223/223, rest 3 files 25/25. Ablations (ablation-replace, each restored blob==HEAD, git diff HEAD empty): A single-value arm back to isPlainObjectValue||isArray -> 35 failed; B element arm likewise -> 21 failed; C door isOptionType->false, objectql rebuilt, dist preflight found marker -> objectql 25 failed + REST 8 failed (unique 409s, stored strings, filter-face pin), restored, rebuilt, preflight --absent clean.",
      "gates": "dispatch-gates --commands (no paths) on 6f164722: 61 derived; all run, exits captured before pipes; --ran reconciliation 61 derived / 61 run / 0 NOT-MEASURED / 0 UNRUN. Two first-run exit 3 re-run exit 0: check-plugin-teardown-shape --self-test (after fetching fixture 621a4876), check:type-check-debt (first run overlapped a closure build). eslint packages/objectql packages/rest --no-inline-config exit 0. CI on PR #29638: in_progress at report time.",
      "dogfood_battery": "6f164722 · os-verify-lock VERDICT command-exit 0 (held 25m45s) · Test Files 398 passed | 3 skipped (401) · Tests 3280 passed | 28 skipped · no failures",
      "mcp_calls": "3 — mcp__github__issue_read get #29562, mcp__github__issue_read get_comments #29562, mcp__github__pull_request_read get_comments #29540 (reads only; no MCP write)",
      "api_writes": "2 — fleet relay pr_create (#29638, 12840 bytes sent == stored); post-stamped os-dev-report comment on #29562. Plus git push (not REST).",
      "files_changed": [
        ".changeset/29562-option-field-scalar-stored-string.md",
        "packages/objectql/src/validation/record-validator.ts",
        "packages/objectql/src/validation/record-validator.option-scalar-value.test.ts (new)",
        "packages/objectql/src/validation/record-validator.select-shape.test.ts (forced: the { free: 3 } control restated)",
        "packages/rest/src/rest-data-option-scalar-value.test.ts (new)"
      ],
      "line_budget": "5 files, +626/-33 vs origin/main (record-validator.ts +124/-33 incl. ~45 lines header realignment and docblocks; tests +477; changeset +16)",
      "deviations": [
        "No driver-memory test consumer added although the delta asks for tests on both drivers: driver-memory's test consumers are a closed census (check:driver-memory-census). Memory was measured in the Step 0 table on every cell; the recording-driver engine pin shows what every driver receives.",
        "Sibling (3) (non-plain object) is not reachable through REST (a JSON body carries none), so its pins are engine-level only.",
        "PR assignee/reviewer not set (standard order: no MCP writes; os-dev: no assignees on the PR).",
        "Session subscribed to PR activity (platform default); no polling."
      ],
      "open_questions": [
        {
          "question": "Filter face (#29486): after this change a row written 3 is stored '3', and { field: 3 } matches no row on SQLite and driver-memory (base matched the row written 3 only); { field: '3' } now matches both. Should the filter door read a number comparand on an option field by its string reading?",
          "options": [
            "A: decide it in #29486 with the text-family twin (same shape after #29241)",
            "B: leave as is: a number comparand on a string-stored field matches nothing"
          ],
          "recommendation": "A, because the text and option families now store the same way and one ruling should cover both; this PR pins today's answer only (rest-data-option-scalar-value.test.ts, last block)."
        },
        {
          "question": "Stored rows: old raw 3 (driver-memory persistence) or SQLite '3.0' rows keep reading as they are and do not collide with new '3'. Should os migrate value-shapes (#29431's family) carry option fields?",
          "options": [
            "A: a separate card: scan option fields and rewrite number/boolean (and SQLite 'N.0') values to the declared code string where one matches",
            "B: no migration; old rows age out"
          ],
          "recommendation": "A as its own card, not here: a SQLite '3.0' row needs the declared codes to decide whether it means '3', which is a migration decision, not a write-door one."
        }
      ],
      "security_findings": "none",
      "out_of_scope_findings": [
        "class: none (observation) · the text arm (BOUNDED_STRING_FIELD_TYPES) still admits a non-plain object: validateRecord({title:new Date(0)}) on a text field is admitted and the door leaves it as sent; this card's ruling is option-family only · carrier: none · noted in PR Acceptance notes 5"
      ]
    }

    Stamped 2026-11-02T23:14Z.

  7. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#1 · os-steve · session_01ADHkmQjPiXgNoSaEpqeGvY

    Release: session_01ADHkmQjPiXgNoSaEpqeGvY · reason: seat 1 off duty on Steve's order (quoted below) · next: pm:queue, follow-up to the next domain:server seat
    Resume-from: claude/project-thread-fp1844@6f164722dffdba3aa22c417e1349f2294625ee52

    Hand-off · PR #29638 · 2026-11-02T23:58Z

    Order: 「server 已经提交了代码的,现在就可以下班。后续跟进交给下一任。」 (Steve, project thread, 2026-11-02T23:54Z).

    State: PR #29638 is reviewed and landed on the seat's side. The narrowing-face record 6591249009 at CONTRACT_REVIEW_TIER on head 6f164722 is PASS with no must-fix. Seat ACCEPT 6591295533 is posted. The PR is ready, auto-merge is on, and it entered the merge queue at 2026-11-02T23:40Z. Nothing is owed on the review.

    What the next seat does:

    1. Follow PR fix(objectql)!: an option field stores the option code string a number or boolean was judged by, and refuses any object (#29562) #29638 to MERGED. If it leaves the queue without merging, read why first. A bystander ejection needs only a re-queue. A red that belongs to this diff needs a fix on claude/project-thread-fp1844, and a moved head needs a Regen-provenance: line before re-queueing.
    2. After the merge, post the landing note here with the merge time as {{WAS:…}}. Fixes #29562 closes the card.
    3. The review's follow-ups are already carried: S2 is the note on filter: a number or boolean comparand on a text-family field never matches, now that a text field stores the number 42 as "42" (#29241), and no error is raised #29486 (6591349330), S3 is migrate(value-shapes): option fields keep old raw number/boolean and SQLite "N.0" rows after #29562; scan and rewrite to the declared code #29643 and S4 is validation(text): a non-plain object (Date, Map, class instance) on a text-family field passes the string arm and is stored as sent #29645. S1 (also name the false/0 half of the SQLite widening in the changeset) is wording only and does not block. It can ride any later edit.
  8. objectstack-fleet commented on Nov 3, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#1 · os-steve · session_01ADHkmQjPiXgNoSaEpqeGvY

    Landed · 2026-11-03T00:02Z

    Probe Reading
    PR #29638 MERGED 2026-11-02T23:59Z, merge commit b391feb1d1, through the merge queue
    #29562 closed completed by Fixes #29562

    PR #29638 merged a minute after the hand-off above, so nothing is left for the next seat on this card. pm:queue comes off. Seat records: ruling 6587559938, review PASS 6591249009 (CONTRACT_REVIEW_TIER, narrowing face), ACCEPT 6591295533 on 6f164722. Follow-ups: the note on #29486 (6591349330), #29643 and #29645.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething 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