Repository navigation
fix(objectql): a currency field written as an object is refused with the bare-number lesson - #27258
objectstack-fleet[bot] wants to merge 2 commits into
Conversation
…the bare-number lesson
The record validator's numeric arm refused `{ value, currency }` on a
currency field with the plain `invalid_number` sentence. It now picks the
spec catalog's `invalid_number_currency_object` sentence for a plain
object on a currency field: a currency value is a bare number, and its
currency is declared in `currencyConfig` or is the tenant default
(ADR-0104). The wire code and the accept set are unchanged.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGufzenS8UJFmncftEGHsd
📓 Docs Drift CheckThis PR changes 2 package(s): 1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 1 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 150 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 a67a8afbfdfa41edbe70200ed0d624d11469a88f && git checkout a67a8afbfdfa41edbe70200ed0d624d11469a88f
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 7858c1d16bb27ac9a5a6d3ae9db9d6dd7331d43d 0dd08045be400cc0e17736de7cb884a180b0616e && git checkout -B drift-repro 7858c1d16bb27ac9a5a6d3ae9db9d6dd7331d43d && git merge --no-ff 0dd08045be400cc0e17736de7cb884a180b0616e
node scripts/docs-audit/affected-docs.mjs --json 7858c1d16bb27ac9a5a6d3ae9db9d6dd7331d43d
|
Contract reviewServed-tier: Inputs read: card #27236 (body; triage 6468450177; claim 6468731037; os-dev-report 6469929253), ruling 6464202770 on #27119 (Q2 = X), card #27182 and PR #27233 (overlap and ordering only), PR #27258 body, file list and the net diff against ① Derived judgmentsEvery accept-set and public-surface change the diff implies, each named right or wrong.
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
…mber clause only ADR-0104 decides that a currency value is a bare number; the tenant default comes from the currency-mode contract, not from the ADR. The cite moves next to the bare-number clause in all four locales and in the changeset's quoted sentence. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WGufzenS8UJFmncftEGHsd
|
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U Contract-review-claim: Triage seat, under gap rule (b) (#27062): this head is over 60 minutes old with no record and no claim; a delta re-review from the lane's PASS on Generated by Claude Code |
Contract reviewServed-tier: Re-review on a moved head. Prior record: the lane's PASS on ① Derived judgments
② Semver level
③ Boundary flags
④ Design judgment
Judged against the ruling it implements (6464202770 Q2 = X), the ADR it cites (0104 D1) and its first consumer (
Implemented-by: VERDICT: PASS Generated by Claude Code |
Requested by Yibo Wang · project thread
Fixes #27236
Ruling-ref: 6464202770
Claim: 6468731037
Clause-②: no
Metadata-protocol: none
The accept set does not move, only the message. Measured: the same four writes (an object,
'abc',10, an object on anumberfield) answer the same verdict and the same wire code before and after; only the first one'smessagechanges. No authorable key, schema, type or export changes.维护者速读
{ value, currency }时,拒收码仍是invalid_number,但提示改为说明:货币值是裸数字(ADR-0104),币种在字段的currencyConfig里声明,dynamic 模式下取组织默认币种。@deprecatedCurrencyValueSchema(still exported atfield.zod.ts:581) — ADR-0104 D1 says deleted; prerequisite of the multi-currency epic #8345 #27182 退役了{ value, currency }这个旧形状,AI 作者和老代码仍会这样写;以前只看到「必须是数字」,不知道该怎么改。packages/objectql/src/validation/record-validator.ts(数值分支那一行fail)、packages/spec/src/system/validation-message.ts(四种语言的新句子)。pnpm --filter @objectstack/objectql exec vitest run src/validation/record-validator.test.ts -t 27236→ 7 个用例全绿。What changes
@objectstack/objectql, the record validator's numeric arm (record-validator.ts,isJudgedNumberType). When the field iscurrencyand the value is a plain object (an object literal or a null-prototype record; never an array, aDateor another class instance), theinvalid_numberrefusal renders the catalog messageinvalid_number_currency_objectinstead of the plaininvalid_numberone. The code staysinvalid_numberand the error carries no new key, so no client contract moves. Everything else in the arm keeps the plain sentence.@objectstack/spec, the validation-message catalog (system/validation-message.ts). It gainsinvalid_number_currency_objectin en, zh-CN, ja-JP and es-ES. One message source: the catalog owns the sentence and the validator passes only its key, so there is no second hand-written copy. A deployment can override it undervalidation.field.invalid_number_currency_object, as with every built-in message.currencyConfig(thefixedmode'sdefaultCurrency) or, underdynamic, the tenant default ([Decision] Currency, all of it: resume multi-currency (#8345) and in what shape, and one rule for an amount whose currency is not known (Studio's USD, the template's USD,fixed's CNY, the example's USD) #27119 Q2 = X). A spec pin checks this in every locale.valueSchemaFor('currency')arm is not touched. The order asked for the sentence there only ifos validateseed data reaches it. Measured: it does not (see Verification). That arm also lives inpackages/spec/src/data/field-value.zod.ts, which PR feat(spec)!: retire the never-consumed CurrencyValueSchema / CurrencyValue export (ADR-0104 D1) #27233 edits, so leaving it alone is also what the serial constraint requires.@deprecatedCurrencyValueSchema(still exported atfield.zod.ts:581) — ADR-0104 D1 says deleted; prerequisite of the multi-currency epic #8345 #27182) is open on the currency surface. This PR edits none of its 26 files (file lists compared at feat(spec)!: retire the never-consumed CurrencyValueSchema / CurrencyValue export (ADR-0104 D1) #27233 headc85e08be). It lands after feat(spec)!: retire the never-consumed CurrencyValueSchema / CurrencyValue export (ADR-0104 D1) #27233.Tests
packages/objectql/src/validation/record-validator.test.ts, a new[#27236]block, 7 cases:{ value: 10, currency: 'USD' }on a dynamic-mode and on a fixed-mode currency field, on insert and on update, is refused as exactly[{ field, code: 'invalid_number', message: LESSON, label }], the same keys as before.currencyConfig.'abc'on a currency field keeps the plain sentence.[10]on a currency field keeps the plain sentence.numberfield keeps the plain sentence, because the lesson is currency-only.packages/spec/src/system/validation-message.test.ts, a new block: every locale names{ value, currency },currencyConfig,defaultCurrency,fixed,dynamicand ADR-0104, and no locale spells a three-letter currency code or a currency symbol. The existing completeness pins (every locale defines every key, every template names{{label}}) cover the new key.Acceptance notes
os validatedoes not judge seed record values against their field type. A seed row giving a currency field{ value: 10, currency: 'USD' }passes validation (exit 0) and is only refused later, when the seed is written through the record validator. This is a separate finding, reported to the seat and not changed here.Verification
All readings below are on head
6d7035fe(branch offorigin/maind745bb5e).The refusal before and after (objectql validator, field def with no label, so the label is the API name)
Before, on
origin/maind745bb5e:After, on
6d7035fe:The error object's keys are
field,code,messageandlabelin every row, before and after.Does os validate seed data reach valueSchemaFor('currency')? No
examples/app-todo, temporarily givenbudget: Field.currency({ label: 'Budget' })and a seed row withbudget: { value: 10, currency: 'USD' }, thennode packages/cli/bin/run.js validateonorigin/maind745bb5e(cli closure built). Result:✓ Validation passed, exit 0. The only line naming the field is thefield-no-consumerswarning, which listsdata[0].records[4].budgetas a carrier site, so the seed row was read and its value was not judged.SeedRecordValueSchema(data/seed.zod.ts) isz.unknown()apart from the seed-file claim. The temporary edits were restored withgit checkout HEAD --andgit diff HEADwas empty afterwards.Ablation
With the fix committed,
node scripts/ablation-replace.mjsreplacedt === 'currency' && isPlainObjectValue(value) ?withfalse /* ablated-27236 */ ?(anchor 1 → 0, marker 0 → 1, blobf47f64b9→845a0720), then ranrecord-validator.test.ts: 2 failed | 131 passed, the tworefuses { value, currency } … with invalid_number and the lessoncases. The restore confirmed blob equals HEAD andgit diff HEADempty. The objectql test imports the validator fromsrc, so no rebuild was needed for that leg. A first attempt was a no-op and is not counted: its replacement textundefinedalready appeared in the file, and the tool refused it before running anything.Package tests and typechecks (under os-verify-lock, each exit captured separately)
pnpm --filter @objectstack/spec test: exit 0, Test Files 743 passed (743), Tests 22265 passed, 1 todo.pnpm --filter @objectstack/spec typecheck: exit 0.pnpm --filter @objectstack/objectql test: exit 0, Test Files 477 passed (477), Tests 8906 passed (8906).pnpm --filter @objectstack/objectql typecheck: exit 0.!in the title, andClause-②has no(narrowing)arm).Derived gates: 82 derived, 81 run with exit 0, 1 NOT MEASURED
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 82 families. Each was run serially and recorded asCMD :: exit N, and--ranreconciled: 82 accounted, 81 run, 1 NOT-MEASURED, 0 UNRUN.pnpm check:dual-build-cjs-loads: exit 3, PREREQUISITE NOT MET (it needs a full-repo build; only the cli and objectql closures were built). NOT MEASURED; CI runs it.node scripts/check-plugin-teardown-shape.mjs --self-test: exit 3 on the first run (the shallow clone lacked its pinned fixture commit), exit 0 aftergit fetch origin 621a4876.check-adr-0087-registration --base origin/main,check-changeset-no-major,check-empty-changeset,check:doc-authoring,check:cross-package-test-inputsand the speccheck:*set, exited 0.Generated by Claude Code