Repository navigation
fix(objectql)!: a string-typed field refuses a Date, a Map, a Set, a class instance or a typed array (#29645) - #29684
Conversation
…class instance or a typed array (#29645) The BOUNDED_STRING_FIELD_TYPES arm refused a plain object or an array, but its object test (isPlainObjectValue) is prototype-strict, so any other object passed on its string reading: driver-memory stored the object itself, SQLite stored a Date as its epoch number and a Map or a Set as "{}". The arm now asks the option arms' test (isObjectValue, #29562), so every object is refused invalid_type with the same value-shape sentence. A string, a number and a boolean keep #29241's rule. Claude-Session: https://claude.ai/code/session_01JQwxbWsjv939FeRpCY83MV Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift Check1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run. What this run could not see
Coarse fallback — 20 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 187705f6348f215b18c2a95a9f211265cc85c18f && git checkout 187705f6348f215b18c2a95a9f211265cc85c18f
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 06881565bdced6c949f05d59f0059a3ff7409924 2e8ba4089ac9fdde45d75931093217b2becd88cb && git checkout -B drift-repro 06881565bdced6c949f05d59f0059a3ff7409924 && git merge --no-ff 2e8ba4089ac9fdde45d75931093217b2becd88cb
node scripts/docs-audit/affected-docs.mjs --json 06881565bdced6c949f05d59f0059a3ff7409924 |
|
domain:server#2 · os-warren · session_01JQwxbXKRzgnVvjzxaYxLUf Contract-review-claim: session_01JQwxbXKRzgnVvjzxaYxLUf · head 2e8ba40 · 2026-11-03T01:19Z Face: |
|
domain:server#2 · os-warren · session_01JQwxbXKRzgnVvjzxaYxLUf Contract reviewServed-tier: Inputs read: card #29645 (body, triage 6591873816, claim 6592073199, os-dev-report 6592841327), PR #29684 (body, 4-file list, net diff against ① Derived judgments
② Semver levelChangeset ③ Boundary flags
④ Design judgmentThe narrowing read against the published spec on
Findings: no must-fix. No should-fix inside the diff. Out of scope, noted not filed: Gates: every check-run on the head has completed; 35 Implemented-by: VERDICT: PASS |
|
domain:server#2 · os-warren · session_01JQwxbXKRzgnVvjzxaYxLUf 维护者速读
席位核对ACCEPT ·
Generated by Claude Code |
Requested by Warren · project thread
Fixes #29645
Clause-②: no (narrowing)
Metadata-protocol: none
Skills-docs: none — no file under
skills/**orcontent/docs/**teaches writing aDate,Map,Set, class instance or typed array into a text-family field (git grepof both trees: zero hits), and the refusal reuses the existing catalog sentence.维护者速读
Date、Map、Set、类实例或类型化数组时,现在一律以invalid_type拒收,与普通对象、数组已有的拒收同一句话;判定改用选项字段(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)已在用的isObjectValue,没有复制新函数。Date存成纪元数字"1767312000000.0",Map/Set存成"{}";email/url/phone 只是碰巧被格式校验拦下。invalid_type。旧行不改写。回滚即 revert 本 PR。packages/objectql/src/validation/record-validator.ts字符串分支isObjectValue(value)那一处;record-validator.text-object-value.test.ts;packages/rest/src/engine-sqlite-text-object-value.test.ts。pnpm --filter @objectstack/objectql exec vitest run --maxWorkers=2 src/validation/record-validator.text-object-value.test.ts,预期 140 个用例全绿。What changes
packages/objectql/src/validation/record-validator.ts, theBOUNDED_STRING_FIELD_TYPESarm: its shape test wasisPlainObjectValue(value) || Array.isArray(value), which is prototype-strict, so aDate,Map,Set, class instance or typed array passed on its string reading. It now asksisObjectValue(value), the option arms' test from 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, and names the shape with that card'sreceivedObjectShape: no copy of either. The wire code (invalid_type), the message key (invalid_value_shape) and the detail (expected a string, received an object/an array) are the ones a plain object already got. It is still a shape test, ahead ofshapeOnly, so a readonly field's value is judged the same way.isObjectValue/receivedObjectShapedocblocks, and 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 "not touched" paragraph ofnormalizeNumericStringValuesnow say "any object".normalizeBlankTypedRowis untouched..changeset/29645-text-field-refuses-any-object.md:@objectstack/objectqlpatch, BREAKING banner, both declaration lines, ADR-0087not-required (no-migration-prescription), FROM / TO and the remedy.Why
Measured on
origin/main@7dae3b3c(base) and on this branch (head2e8ba408), through the engine's insert, update by id and update by predicate, on SQLite (the physical column, read with the driver's query builder) and driver-memory (the engine's read). Twelve types × five values × three doors × two drivers:new Date(...)"1767312000000.0"Dateitselfinvalid_email/invalid_url/invalid_phoneinvalid_typenew Map(...)"{}"Mapinvalid_typenew Set(...)"{}"Setinvalid_type{ k: 1 }'{"k":1}'invalid_typenew Uint8Array([1, 2])'{"0":1,"1":2}'invalid_typepasswordreads back masked on both sides, so its stored column is the only answer there. The format types refused by accident:String(value)failed their regex. Head refuses the shape first on all twelve, so those three answerinvalid_typeinstead of their format code.Tests
packages/objectql/src/validation/record-validator.text-object-value.test.ts(140 cases): the spec set's twelve members pinned by name; each type ×Date/Map/Set/ class instance / typed array × insert / update refuses withcode+constraint.type+ detail; a per-type plain-string control (a valid address, URL and phone on the three format types); the message names field, type and shape; the readonly scope refuses; a number and a boolean keep 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 (written as their string, then judged); the door leaves an object as sent; ajsonfield still holds aDateor aMap; engine insert, insertMany, update by id and update by predicate answerVALIDATION_FAILEDand the driver is never written, with a string control that is.packages/rest/src/engine-sqlite-text-object-value.test.ts(6 cases): the same five values on a realObjectQL+ sqliteSqlDriver, all three doors refused, the physical row absent or unchanged; control: a string stored as sent and a number stored as"42".Acceptance notes
currencyarm'sisPlainObjectValue(sentence choice only, never the verdict) is untouched and is the helper's one remaining caller.Verification
All readings on head
2e8ba408, base7dae3b3c.Suites
@objectstack/objectqltest: Test Files 544 passed (544), Tests 10741 passed (10741); lock VERDICT command-exit 0.@objectstack/resttest: Test Files 381 passed (381), Tests 7130 passed, 332 skipped (7462); lock VERDICT command-exit 0.@objectstack/objectqland@objectstack/resttypecheck (both includecheck:test-typecheck): lock VERDICT command-exit 0.@objectstack/dogfood(full battery, narrowing arm), after building the@objectstack/dogfood^...closure: Test Files 399 passed, 3 skipped (402); Tests 3297 passed, 28 skipped (3325); lock VERDICT command-exit 0. No failure.pnpm lint(eslint . --no-inline-config): exit 0.Ablation
scripts/ablation-replace.mjs(child mode, trap-armed) put the guard back toif (isPlainObjectValue(value) || Array.isArray(value)) {: anchor 1 → 0, replacement 0 → 1, blob7b789eee→61b1bb58. The objectql file (validator imported fromsrc) ran red: Tests 123 failed, 17 passed (140); the 17 green are the controls and leave-alone cases. The objectql build was then run so the rest pin would read the mutation: its JS emitted and its DTS step failed (the mutated line handsunknowntoreceivedObjectShape), andablation-dist-preflightfound the marker in 4 built files. The rest pin ran red: Tests 5 failed, 1 passed (6), the green one being the control. Restore: blob == HEAD7b789eee,git diff HEADempty; rebuild exit 0,ablation-dist-preflight --absentclean on all 14 built files and the tree; the rest pin back to 6/6.Gates
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackat2e8ba408derived 60 families; all 60 ran, each exit 0.--ranreconciliation: 60 derived, 60 run, 0 NOT-MEASURED, 0 UNRUN (pnpm lintrecorded outside the derivation). Four first answered a container prerequisite and were rerun green:check-plugin-teardown-shape --self-testexit 3 (aftergit fetch --depth=1of its fixture commit),check:dts-closureexit 1 (a dist mid-rebuild by the concurrent dogfood closure build; 225/225 declaration files on rerun),check:type-check-debtexit 3 (verify-lock queue timeout behind the dogfood run; rerun under the lock),check:dual-build-cjs-loadsexit 3 (seven unbuilt packages; rerun after the full build). CI families: this PR's check-runs.Human Minimum
packages/specholds no runtime logic (nopackages/spechunk).pnpm lintpasses.main, one pull request.packages/spectouched: n/a, untouched.content/docs/references/,packages/*/CHANGELOG.mdorcontent/docs/releases/.Generated by Claude Code