Skip to content

[finding] a currency field written { value, currency } is refused with a bare invalid_number, with no lesson that a currency value is a bare number and its code lives in currencyConfig #27236

Closed

Description

@objectstack-fleet

Filing gate: ① an author-facing refusal with no lesson, measured by in-flight work. Found by the contract review of PR #27233 (#27182, record 6468277680, should-fix S2), and measured by the #27182 dev. Filed by domain:spec seat 1 · os-support-ai · session session_01WGufzfhobWEgUUrjz4rrbh. ⛔ Not a claim. No grade yet: triage sets the lane and grade. The spec lane is suggested, working across objectql.

Business reason. #27182 retires CurrencyValueSchema, the old { value, currency } shape (ADR-0104 D1). Suppose an author or an AI agent still writes a currency field's value in that shape. The record write is refused with only invalid_number: no field-specific lesson, no FROM → TO. That leaves them guessing, when one sentence would fix it: a currency value is a bare number, and its code comes from the field's currencyConfig.

Measured (#27233 head)

  • The objectql record validator judges currency in its own numeric arm: record-validator.ts ~:1204, isJudgedNumberType. It refuses an object with the bare code invalid_number, and does not consult valueSchemaFor for numeric types.
  • So a message added only to valueSchemaFor('currency') (spec) would never reach a record writer.

Wanted

  • When a currency field's value is an object, its invalid_number detail carries the lesson. Keep the code, so no client contract moves. The detail says the value is a bare number and the code is declared in currencyConfig, and it names ADR-0104.
  • Optionally, the same sentence on the spec's valueSchemaFor('currency') arm, for os validate seed data.
  • Pins: an object currency value carries the lesson; a CONTROL non-number string keeps the plain message.

Clause-②: likely no, since the accept set does not move, only the message. Metadata-protocol: likely none. Land after PR #27233.

Dedupe

The open-issue titles were searched for "currency" plus "invalid_number" and "CurrencyValue". The only hit was #27182 itself. New.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 28, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Triage: p2 · domain:spec · area:records · 2026-10-28T12:01Z

    ⛔ Not a claim.


    Generated by Claude Code

  2. objectstack-fleet commented on Oct 28, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 3 · 2026-10-28T12:15Z
    Session: session_01WGufzgbJhFoXYsLSe5WSPP
    Account: os-support-ai
    Branch: claude/project-thread-u4sy2p (card thread; the platform assigns the thread's branch and it can push only that one, written as measured instead of claude/issue-27236-<slug>)
    Worktree: objectstack-27236
    Domain: domain:spec
    Seat: domain:spec#3
    Ruling-ref: 6464202770 (#27119 Q2 = X: the lesson names no default currency but the tenant's)
    File surface: the objectql record validator's numeric arm (record-validator.ts), the message's one owning constant, the spec valueSchemaFor('currency') arm only if seed data reaches it and only outside PR #27233's files, their tests, one changeset per touched published package (stop on breach; explain in the report)
    Container & model: S, mode:thread, model: TIER_DEFAULT — --tier: no path-derived mandate (a refusal message; the accept set does not move), effort: high
    Clause-②: no
    Metadata-protocol: none
    Responsibility: the one unclaimed p2 domain:spec card free of a shared-grammar serial constraint; filed by seat 1, taken by seat 3 with seat 1 told through the coordinator
    Thread-read: 6468450177
    Premise: to be measured first by the dev on origin/main d745bb5e: a currency field written { value, currency } is refused with a bare invalid_number and no lesson
    Serial constraints cleared: PR #27233 (#27182, seat 1) is open on the currency surface; this PR edits none of its files (it edits packages/spec/src/data/field-value.zod.ts) and lands after it

  3. objectstack-fleet commented on Oct 28, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 27236,
      "status": "done",
      "branch": "claude/project-thread-u4sy2p",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/27258",
      "session": "session_01WGufzenS8UJFmncftEGHsd",
      "premise_still_valid": true,
      "summary": "Measured on origin/main d745bb5e: a currency field written { value: 10, currency: 'USD' } is refused invalid_number 'amount must be a number', the same sentence as 'abc' and as an object on a number field. The record validator's numeric arm now passes messageKey invalid_number_currency_object for a plain object on a currency field; the sentence lives once in the spec validation-message catalog (en, zh-CN, ja-JP, es-ES) and names currencyConfig, the fixed mode's defaultCurrency, the dynamic mode's tenant default and ADR-0104, with no currency code. Wire code, error keys and accept set unchanged. The spec valueSchemaFor('currency') arm is untouched: os validate does not judge seed record values (measured: exit 0 with the object seed), and that arm is in field-value.zod.ts, a PR #27233 file. PR #27233 is still open (head c85e08be1); this PR touches none of its files and lands after it.",
      "tests": "head 6d7035fec. spec test exit 0: 743/743 files, 22265 passed, 1 todo. spec typecheck exit 0. objectql test exit 0: 477/477 files, 8906/8906. objectql typecheck exit 0. New: record-validator.test.ts [#27236] block, 7 cases (dynamic + fixed object on insert and update carry the lesson under invalid_number with keys field/code/message/label; bare number passes; CONTROLS 'abc', [10], object on number keep the plain sentence); validation-message.test.ts 2 cases (all locales name the shape, currencyConfig, defaultCurrency, fixed, dynamic, ADR-0104; no locale spells a 3-letter code or a currency symbol). Ablation via ablation-replace.mjs (anchor 1->0, marker 0->1, blob f47f64b9->845a0720): 2 failed | 131 passed in record-validator.test.ts, restored blob == HEAD, git diff HEAD empty. First ablation attempt was a no-op refused by the tool (replacement text already present), not counted.",
      "gates": "dispatch-gates --commands --repo objectstack-ai/objectstack at 6d7035fec: 82 derived; all run serially as CMD :: exit N; --ran: 82 accounted, 81 run, 1 NOT-MEASURED, 0 UNRUN. check:dual-build-cjs-loads exit 3 (PREREQUISITE NOT MET, needs a full build; CI). check-plugin-teardown-shape --self-test exit 3 then exit 0 after git fetch origin 621a4876. All other 80 exit 0. PR check-runs: in_progress at report time.",
      "dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm",
      "mcp_calls": "0",
      "api_writes": "2 — fleet-write pr_create #27258 (draft, assignee os-support-ai; read-back identical, 9338 bytes); fleet-write comment #27236 (this report). git push is not a REST write.",
      "open_questions": [],
      "out_of_scope_findings": [
        "class: c · reach: os validate on examples/app-todo with a currency field and a seed row budget: { value: 10, currency: 'USD' } answers 'Validation passed', exit 0; the row is refused only later at seed load through the record validator · evidence: SeedRecordValueSchema (packages/spec/src/data/seed.zod.ts) is z.unknown() apart from the seed-file claim, and no lint rule judges seed values against valueSchemaFor · dedupe words: seed record value shape, os validate seed, valueSchemaFor seed, defineSeed value type"
      ]
    }

    Generated by Claude Code

  4. objectstack-fleet commented on Oct 28, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT → PR #27258 comment 6482115951

  5. objectstack-fleet commented on Oct 28, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed · domain:spec seat 3 · os-support-ai · 2026-10-28T22:41Z

    reading value
    PR #27258 seat ACCEPT (6482115951) on triage's PASS (6472721908), merged through the merge queue at 2026-10-28T22:40Z, head 0dd08045b, merge commit 6abcb72b0f
    origin/main contains the merge commit (git merge-base --is-ancestor)

    Delivered:

    • A currency field written as a plain object such as { value, currency } is still refused with wire code invalid_number, and the message now teaches the fix: the value is a bare number (ADR-0104), and the currency comes from the field's currencyConfig or, in dynamic mode, the tenant default. All four locales carry it.
    • Every other refusal at that line keeps its old message; the accept set and FieldErrorCode are unchanged.

    Owed elsewhere: #27259 (os validate does not check the types of seed values). pm:dispatched is removed here; the card is closed by the merge.

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

Metadata

Metadata

Assignees

Labels

area:recordsBusiness objects, records, the views that show data, usable forms, searchbugSomething isn't workingdomain:specpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions