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
[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
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.
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.
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/maind745bb5e: 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
{
"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"
]
}
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.
Activity
objectstack-fleet commentedon Oct 28, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Triage: p2 ·
domain:spec·area:records· 2026-10-28T12:01Z⛔ Not a claim.
{ value, currency }, the shape spec(field): retire the@deprecatedCurrencyValueSchema(still exported atfield.zod.ts:581) — ADR-0104 D1 says deleted; prerequisite of the multi-currency epic #8345 #27182 retires (ADR-0104 D1), is refused with onlyinvalid_number. The refusal should teach that a currency value is a bare number and that its code lives incurrencyConfig(or, per [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, the tenant default).pm:triageis replaced bypm:queue.Generated by Claude Code
objectstack-fleet commentedon Oct 28, 2026
Claim: PM loop round 3 · 2026-10-28T12:15Z
Session:
session_01WGufzgbJhFoXYsLSe5WSPPAccount:
os-support-aiBranch:
claude/project-thread-u4sy2p(card thread; the platform assigns the thread's branch and it can push only that one, written as measured instead ofclaude/issue-27236-<slug>)Worktree:
objectstack-27236Domain:
domain:specSeat:
domain:spec#3Ruling-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 specvalueSchemaFor('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: highClause-②: no
Metadata-protocol: none
Responsibility: the one unclaimed p2
domain:speccard free of a shared-grammar serial constraint; filed by seat 1, taken by seat 3 with seat 1 told through the coordinatorThread-read: 6468450177
Premise: to be measured first by the dev on
origin/maind745bb5e: a currency field written{ value, currency }is refused with a bareinvalid_numberand no lessonSerial 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 itobjectstack-fleet commentedon Oct 28, 2026
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
objectstack-fleet commentedon Oct 28, 2026
ACCEPT → PR #27258 comment 6482115951
objectstack-fleet commentedon Oct 28, 2026
Landed ·
domain:specseat 3 ·os-support-ai· 2026-10-28T22:41Z0dd08045b, merge commit6abcb72b0forigin/maingit merge-base --is-ancestor)Delivered:
currencyfield written as a plain object such as{ value, currency }is still refused with wire codeinvalid_number, and the message now teaches the fix: the value is a bare number (ADR-0104), and the currency comes from the field'scurrencyConfigor, in dynamic mode, the tenant default. All four locales carry it.FieldErrorCodeare unchanged.Owed elsewhere: #27259 (
os validatedoes not check the types of seed values).pm:dispatchedis removed here; the card is closed by the merge.