Repository navigation
Description
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)
- Single-value select/radio: a number or boolean is stored as sent.
- Multi-option fields (
multiselect,checkboxes,tags, and the rest ofMULTI_OPTION_TYPES): a number or boolean element is stored as sent. - A non-plain object (
Date,Map, a class instance) on a free-form select is not caught byisPlainObjectValue, 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)
- A: store the judged string, the way records: a number or a boolean written to a text-family field is admitted on its string reading but stored as the raw number/boolean, so a
textcolumn hands back42, not"42"#29241 does for text.3becomes"3"andtruebecomes"true". Membership is then judged on the string. A declared-options select with code"3"admits3and stores"3". - B: refuse it with
invalid_typeand the shape sentence, the way PR fix(objectql)!: a select refuses a plain object or an array as its value, and as a multi-value element (#29244) #29540 refuses an object.
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
test(objectql): mark the number-on-select control as the present-beha…
objectstack-fleet commentedon Nov 2, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: none
Triage: p2 ·
pm:queue· 2026-11-02T18:59Z⛔ Not a claim.
3beside"3"), and filters, picklists and reports split with it. The option-family twin of records: a number or a boolean written to a text-family field is admitted on its string reading but stored as the raw number/boolean, so atextcolumn hands back42, not"42"#29241, p2. Its filter-side reading waits 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's decision.Generated by Claude Code
objectstack-fleet commentedon Nov 2, 2026
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.
String(value), the same reading the records: a number or a boolean written to a text-family field is admitted on its string reading but stored as the raw number/boolean, so atextcolumn hands back42, not"42"#29241 text arm uses. A number or boolean on a single select or radio, and a number or boolean element on a multi-option field, are stored as their string. Option membership is judged on that string, so a declared code"3"admits3, and no matching code is refusedinvalid_optionas before.Date,Map, class instance) on an option-family field is refusedinvalid_typewith the records: a free-formselectfield (nooptions) 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 value-shape sentence, never stringified.Clause-②: yes (narrowing). A unique answer moves (3beside"3"now collide, as records: a number or a boolean written to a text-family field is admitted on its string reading but stored as the raw number/boolean, so atextcolumn hands back42, not"42"#29241's did), and a non-plain object is newly refused. If the build finds a product writer that relies on a stored number, it stops and reports, and the seat re-rules.objectstack-fleet commentedon Nov 2, 2026
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 sessionAccount:
os-steveBranch: 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:serverSeat:
domain:server#1Ruling-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 inrecord-validator.select-shape.test.ts), and an@objectstack/objectqlpatch changeset. ⛔ Nopackages/spec/**, no new error code, no filter-door change (#29486), nopackages/metadata-protocol/src/protocol.ts(seat 5 freeze).Container & model:
M,mode:thread, model as the thread was opened,effort: mediumClause-②: 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 (
3beside"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 fieldThread-read: 6587559938
Premise: as filed; #29540 (the object/array rule this extends) merged as
e832ddb1f5Serial constraints cleared: no in-flight card of this seat touches
record-validator.ts. #29068 is inservice-messaging/runtime/client, and #29568 (PR #29587) is in runtimepackages.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).
objectstack-fleet commentedon Nov 2, 2026
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_01ADHkmSLV8FibVzx9B1ZHNnAccount:
os-steveBranch:
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:serverSeat:
domain:server#1Ruling-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 inrecord-validator.select-shape.test.ts), and an@objectstack/objectqlpatch changeset. ⛔ Nopackages/spec/**, no new error code, no filter-door change (#29486), nopackages/metadata-protocol/src/protocol.ts(seat 5 freeze).Container & model:
M,mode:thread, model as the thread was opened,effort: mediumClause-②: 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 (
3beside"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 fieldThread-read: 6587559938
Premise: as filed; #29540 (the object/array rule this extends) merged as
e832ddb1f5Serial constraints cleared: no in-flight card of this seat touches
record-validator.ts. #29068 is inservice-messaging/runtime/client, and #29568 (PR #29587) is in runtimepackages.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).
test(objectql,rest): pin option-family string storage, object refusal…
Merge origin/main into claude/project-thread-fp1844 (#29562)
objectstack-fleet commentedon Nov 2, 2026
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.
objectstack-fleet commentedon Nov 2, 2026
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 nextdomain:serverseatResume-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_TIERon head6f164722is 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:
claude/project-thread-fp1844, and a moved head needs aRegen-provenance:line before re-queueing.{{WAS:…}}.Fixes #29562closes the card.false/0half of the SQLite widening in the changeset) is wording only and does not block. It can ride any later edit.objectstack-fleet commentedon Nov 3, 2026
domain:server#1 · os-steve · session_01ADHkmQjPiXgNoSaEpqeGvY
Landed · 2026-11-03T00:02Z
b391feb1d1, through the merge queueFixes #29562PR #29638 merged a minute after the hand-off above, so nothing is left for the next seat on this card.
pm:queuecomes off. Seat records: ruling 6587559938, review PASS 6591249009 (CONTRACT_REVIEW_TIER, narrowing face), ACCEPT 6591295533 on6f164722. Follow-ups: the note on #29486 (6591349330), #29643 and #29645.