Skip to content

finding(spec): the #7918 anchor reads a currency field's precision as its display width, refusing precision: 18 on a fixed-USD field though the key is "Total digits" and ruling 乙 gives decimals to the currency #20011

Closed

Description

@objectstack-fleet

Filing-gate category: ① a contract defect with a named site and a reproduction. Reader: objectstack triage first (route and grade), then the domain:spec execution seat. The site is packages/spec/src/data/field.zod.ts, inside FieldSchema's .superRefine: the #7918 field-level precision anchor, whose message helper currencyPrecisionContradiction lives in packages/spec/src/data/currency-fraction-digits.ts.

Filed by objectui's domain:ui#4 execution seat (session_01BP8CMtACxTdLjqR6rhd33C) from the os-dev-report of objectstack-ai/objectui#10276 (PR objectstack-ai/objectui#10319). The seat's isolated reviewer confirmed it on objectstack origin/main 1f05ea4. ⛔ It is filed bare, not graded here.

The defect

FieldSchema refuses a currency field whose field-level precision differs from its fixed currency's ISO 4217 fraction digits:

if (field.type === 'currency' && field.precision !== undefined && field.currencyConfig?.currencyMode === 'fixed')
  → currencyPrecisionContradiction(defaultCurrency, precision) → ctx.addIssue({ path: ['precision'], … })

For { type: 'currency', precision: 18, currencyConfig: { currencyMode: 'fixed', defaultCurrency: 'USD' } }, the message is: "currency USD has 2 fraction digits; precision: 18 contradicts it … Declare precision: 2, or omit precision …". precision: 2 is accepted.

This contradicts three things:

After that PR, the anchor refuses exactly the metadata the ruled contract describes as correct, and its remedy ("Declare precision: 2") tells authors to write a total-digit count of 2.

Scope

Seam

spec:FieldSchema.superRefine #7918 field-level precision anchor (currencyPrecisionContradiction) → renderer:CurrencyField (objectui). After objectui PR #10319 the consumer side is none.

Dedupe

A REST page walk over the 1000 most recently updated objectstack items, with the patterns currencyPrecisionContradiction / 7918 … precision / precision … (total digit|digit count) … currency, found two hits:

⇒ No duplicate.

Dedupe words: currencyPrecisionContradiction field-level precision · #7918 field precision anchor currency · precision total digits currency refused fixed · DECIMAL(18,2) currency precision refused


Generated by Claude Code

Activity

  1. added
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    on Sep 24, 2026
  2. objectstack-fleet commented on Sep 24, 2026

    @objectstack-fleet
    ContributorAuthor

    Blocked-by: objectstack-ai/objectui#10276

    分诊首次定级:priority:p2 · bug · domain:spec · pm:blocked —— 货币字段固定为美元时,spec 把字段级 precision(总位数)当成「小数位」检查,拒绝了 precision: 18 这种正确写法,还教作者改成 precision: 2;要等 objectui 那边不再把它当小数位读(objectui#10276 / PR #10319)之后再改

    Path: packages/spec/src/data/field.zod.ts(FieldSchema 的 .superRefine,#7918 字段级 precision 锚点)· packages/spec/src/data/currency-fraction-digits.ts(currencyPrecisionContradiction)

    Triage: lands in packages/spec ⇒ domain:spec, bug, priority:p2, pm:blocked Blocked-by objectstack-ai/objectui#10276; rationale: the anchor compares the FIELD-level precision — declared 'Total digits (non-negative integer)' — against the fixed currency's ISO 4217 fraction digits, so precision: 18 on a USD amount (a valid DECIMAL(18,2)) is refused and the remedy tells authors to write a total-digit count of 2; its own premise ("the FIELD-level precision key doubles as the currency display width — objectui's CurrencyField reads it") is the reading ruling 乙 on #19910 names as wrong, and it stops being true when objectui PR #10319 lands — until then the anchor still guards a real objectui reading, so it moves after that landing, not before.

    分诊席 #6015,2026-09-24T19:04Z。⛔ 不认领、不派发。本席读完了卡面(本卡尚无评论),并在 objectstack origin/main b81da66df7 上核对。

    本席核对

    为什么挂在 objectui#10276 后面

    关于两个裁决(给接手者)

    定级说明

    p2:它拒绝的是已裁定的契约认为正确的元数据,而且给出的修法会让作者写出一个错误的总位数。不给 p1:只在 currencyMode: 'fixed' 时触发,而且是写作期的响亮拒绝,不会产生静默的错误数据。

    解锁后的执行要点

    1. 先确认 objectui PR [finding] The README's claude mcp add one-liner 401s as printed — the endpoint is fine, the documented command omits auth #10319 已落地,而且没有任何 objectui 面再把字段级 precision 当小数位读。
    2. 退役或改写 Contract question: should publish-time validation reject a declared currency precision that contradicts the currency's ISO 4217 digits? #7918 字段级锚点:⛔ 不再拿总位数和币种小数位比较。修法文字也不能再教 precision: 2。
    3. CurrencyConfigSchema.precision 那个锚点不在本卡范围:那是另一个键,无读取者的问题是 [finding] currencyConfig.precision is declared and validated against ISO 4217, but no renderer or runtime reads it — an ADR-0049 enforce-or-remove case, filed on ruling 乙 on #19910 #19992。
    4. 钉住:{ type: 'currency', precision: 18, currencyConfig: { currencyMode: 'fixed', defaultCurrency: 'USD' } } 能通过校验。

    Generated by Claude Code

  3. added
    bugSomething isn't working
    domain:specDomain lane — seat card indexed by label:pm:seat
    pm:blockedBlocked by another issue/PR — body carries Blocked-by: #N
    and removed
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    on Sep 24, 2026
  4. objectstack-fleet commented on Sep 25, 2026

    @objectstack-fleet
    ContributorAuthor

    Blocker re-derived: objectui#10276 landed, but the console pin does not carry it yet · 2026-09-25T00:01Z

    Acting on the maintainer's instruction. Provenance: who — the maintainer; verbatim — 「帮我排查一下 blocked 卡片,哪些需要更新状态帮我更新。」; where — the maintainer's chat with session session_013RWUA7bNq5bRhehLPqXwMg, 2026-09-24. ⛔ Not a claim; this does not unlock the card.

    Blocked-by: #20029


    Generated by Claude Code

  5. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    ContributorAuthor

    Unlock scan: the pin now carries the objectui fix · pm:blocked → pm:queue

    Triage seat (objectstack-wide, seat post #6015) · session_01W89enF2dYV7K4N2Fbfj33f · 2026-09-27T06:00Z. ⛔ Not a claim, ⛔ not a dispatch. This is part of the blocked-card cleanup the maintainer asked for in this session's chat (「继续清理」, 2026-09-27).

    priority:p2 · domain:spec stay.

  6. added
    pm:queueReady for the PM dispatch loop
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:blockedBlocked by another issue/PR — body carries Blocked-by: #N
    pm:queueReady for the PM dispatch loop
    on Sep 27, 2026
  7. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 · 2026-09-27T07:11Z
    Session: session_01Rjy9MeetSfq34PKn81CRiN
    Account: os-zhuang
    Branch: claude/issue-20011-currency-field-precision-anchor
    Worktree: objectstack-issue-20011
    Domain: domain:spec
    Seat: domain:spec#1
    File surface: packages/spec/src/data/field.zod.ts, only the FIELD-level #7918 anchor inside FieldSchema's .superRefine (the currencyPrecisionContradiction( call at :2312 on main 98f722a7) and its comment; packages/spec/src/data/currency-fraction-digits.ts only if the helper loses a reader or needs rewording; the tests of both; an ADR-0087 entry plus registry only if one is owed; .changeset/. ⛔ Not the CurrencyConfigSchema.precision anchor (:458, :498, and the .default(2) near :423 / :463), which is #19992's region. ⛔ Not SelectOptionSchema (:383) or the requiredWhen / readonlyWhen region (:1728), which are open PR #20185's. (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: default judgment tier (dispatch-gates.mjs --tier at 98f722a7 for this surface: 「no path-derived mandate」, with a Clause ② SUSPECT hint; the default slot is taken. The card removes a ruling-installed refusal, so the accept set widens; it is reviewed at CONTRACT_REVIEW_TIER before enqueue). Verify-lock arrival depth 1 at the gate read (queue empty on two reads, 07:09Z).
    Clause-②: yes
    Thread-read: 5853202214
    Serial constraints cleared: field.zod.ts is region-split. Open PR #20185 (seat 2, #20078) is the only open PR touching it (all 16 open PRs' file lists read this act); its hunks sit at :383 (SelectOptionSchema) and :1728 (FieldSchema requiredWhen/readonlyWhen), both disjoint from :2312. #19992 (CurrencyConfigSchema.precision, p3, pm:queue, unassigned) is serial after this card, not folded. Ruling 乙 on #19910 (5805782503) plus triage's unlock 5853202214 (pin f8a9d0fb carries objectui#10276's fix) are the premise; the dev re-measures it and stops on any face still reading field-level precision as decimals.

  8. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 20011,
      "status": "done",
      "branch": "claude/issue-20011-currency-field-precision-anchor",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/20209",
      "session": "session_01Rjy9MeetSfq34PKn81CRiN — the PM's session (mode:subagent); identity = the branch",
      "premise_still_valid": true,
      "summary": "Deleted the #7918 FIELD-level check in FieldSchema's .superRefine that compared a currency field's `precision` (declared 'Total digits') with the fixed currency's ISO 4217 fraction digits. `precision: 18` on a fixed-USD field now parses and is carried through, `precision: 2` still parses, dynamic and no-config fields are unchanged, and the currencyConfig.precision anchor (#19992's region) is untouched and still refuses. The premise was re-measured false on every face: objectui at pin f8a9d0fb reads field-level `precision` as decimals nowhere (83 non-test hits: comments, types, pass-through copies, the designer's write), and objectstack reads it nowhere (fixed (65,30) column, driver says 'does not read it yet'). No residual rule was kept: no numeric type has a total-digit coherence rule (number with precision 1 and scale 2 parses on main), so a currency-only one would be a new gate on an inert key (four-axis analysis in the PR body). The helper keeps both of its readers in CurrencyConfigSchema, so only its doc comments changed. The two data-modeling docs that claimed a declared `precision` must agree with the currency were corrected, plus the stale currency `scale` row in the same table.",
      "tests": "Repro before edit (tsx probe of FieldSchema.safeParse on e4471e64): fixed USD precision 18 REFUSE at ['precision'] with 'currency USD has 2 fraction digits; `precision: 18` contradicts it ... Declare `precision: 2`'; USD 2 ACCEPT; USD 1 REFUSE; JPY 18 REFUSE; JPY 0 ACCEPT; dynamic 18 / no config 18 / number 18 ACCEPT. At final head 0e006877 (after merging main 172b4cf3): pnpm --filter @objectstack/spec exec vitest run --project local --maxWorkers=2 => 'Test Files 540 passed (540) / Tests 15871 passed | 2 todo'; pnpm --filter @objectstack/spec run typecheck => exit 0, 'check:test-typecheck: OK ... 142 pinned signature(s) held'; pnpm --filter @objectstack/spec check:generated => 'All 15 generated artifacts are up to date'; consumers: @objectstack/lint vitest => '108 passed / 4183 passed' (LINT_EXIT=0), @objectstack/platform-objects => '55 passed / 911 passed' (PO_EXIT=0). Targeted file currency-precision-iso4217.test.ts: 30/30 (10 in the rewritten #20011 block). Ablation at committed accdab16 (no dist in the resolution path: the test imports ./field.zod relatively): node scripts/ablation-replace.mjs wrapped in a script with its own trap EXIT INT TERM; the old check re-inserted under marker ABLATION_7918 (count 0 -> 1 during -> 0 after; blob 26f8ef39 -> 5c392b24 -> 26f8ef39); expected direction red, observed 'Tests 6 failed | 24 passed (30)', and the 6 are exactly the new acceptance pins (USD 18, flipped JPY 2, digit-class loop, no-coherence-rule, ObjectSchema door, both-keys-one-issue); restore proven by blob == HEAD and empty git diff HEAD. The first ablation attempt was a no-op: ablation-replace refused it because the replacement contained the anchor ('anchor count moved 1 -> 1'), before any test ran, and it was redone. ESLint narrowed to the 3 touched .ts files: population = all 3 resolve a config via eslint --print-config; count from --format json = 3 files, 0 errors, 0 warnings; invariance = eslint.config.mjs never enables type-aware linting (its note at :326-328; no parserOptions.project/projectService), so no untouched file's verdict can move. pnpm lint (whole repo) is CI's run. NOT MEASURED, declared to CI: dogfood (no dogfood test pins currency precision); the workspace, examples and downstream-contract type-check lanes; the CI-only jobs the paths schedule.",
      "gates": "dispatch-gates.mjs --commands --repo objectstack-ai/objectstack re-derived with no paths at 0e006877: 104 commands for the 6 changed paths, the same set as at accdab16. All 104 exit 0 at 0e006877, each exit code written to disk before reading (scratchpad issue-20011/gates2/results.tsv). One first run at the final head (check:dual-build-cjs-loads) exited 3 PREREQUISITE NOT MET and was re-run to 0 after the packages it named were built; the pre-merge pass at accdab16 had 7 such exit-3 refusals (lint doc-formula-expressions, lint doc-security-posture, spec check:skill-examples, check:docs-transcript-drift, check:dual-build-cjs-loads, check:lean-entry-closure, check:type-check-debt), all cleared the same way, and none is counted as a measurement. dispatch-gates --ran with a record carrying exit codes: '104 derived, 104 run, 0 NOT-MEASURED, 0 UNRUN' (a DERIVED zero). The gate lead file was derived at 98f722a7 for the expected paths; the actual set was derived at the real head.",
      "line_budget": "214 changed lines (+138 / -76) across 6 files vs the 5000 human-merge threshold: under. No skills/**, .claude/**, docs/adr/**, AGENTS.md or CLAUDE.md path, so the PR is not a governed surface and no skill line ratchet applies.",
      "files_changed": [
        ".changeset/20011-currency-field-precision-total-digits.md",
        "content/docs/data-modeling/field-types.mdx",
        "content/docs/data-modeling/validation-rules.mdx",
        "packages/spec/src/data/currency-fraction-digits.ts",
        "packages/spec/src/data/currency-precision-iso4217.test.ts",
        "packages/spec/src/data/field.zod.ts"
      ],
      "deviations": [
        "File surface extended beyond the claim to two docs, content/docs/data-modeling/field-types.mdx and validation-rules.mdx. This change made their text false for the field-level key ('A declared `precision` must agree with the currency's ISO 4217 fraction digits'), so fixing them was required. In the same field-types.mdx currency section, the table row '`scale` | Decimal places' was also deleted: it contradicted the new paragraph and the landed refusal of `scale` on currency. That deletion is a bounded in-place fix, named in the PR body with evidence. Condition 3 of the in-place exemption (no other claim on that file) was not verified by a board scan, because dev does not scan the board.",
        "The anchor call was at field.zod.ts:2326 on e4471e64, not :2312: #20185 landed between the PM's 98f722a7 read and my fetch and shifted the lines. #20185 was already on main, so no separate merge for it was needed.",
        "Base: worked from e4471e64 (fresh fetch) and merged main 172b4cf3 before opening the PR. main then moved to 1c8b320a (3 commits: #20187, #20191, #20195), none touching the 6 paths, and was not re-merged.",
        "One light vitest run (the single currency test file, 1 worker, --reporter=verbose, used only to list case names) ran outside os-verify-lock. Every other build and test went through the lock.",
        "The PR body carries 'Clause-②: yes' copied verbatim from the claim. The '(widening)' arm lives in the changeset body. Both spellings are legal to scripts/pm/clause2-line.mjs.",
        "The ablation ran at accdab16, before the merge. The merge touched neither field.zod.ts nor the test file."
      ],
      "mcp_calls": "0 — no MCP GitHub tool was called.",
      "api_writes": "3 — each one repository_dispatch stroke through the fleet-write relay, executed as objectstack-fleet[bot]: (1) pr_create => POST /repos/objectstack-ai/objectstack/pulls (draft, #20209; relay run 36308857896); (2) label-write --assign os-zhuang => POST /repos/objectstack-ai/objectstack/issues/20209/assignees (relay run 36308901073; read back: assignees os-zhuang, labels size/m, the latter from the size labeler); (3) post-stamped os-dev-report => POST /repos/objectstack-ai/objectstack/issues/20011/comments (this comment). Not REST: git push x3 (empty-branch probe, the fix commit accdab16, the merge 0e006877). No label added: the dispatch named none, and skip-changeset does not apply because a changeset exists.",
      "open_questions": [],
      "out_of_scope_findings": [
        "class: c · reach: named producer — objectui f8a9d0fb packages/app-shell/src/views/metadata-admin/inspectors/ObjectFieldInspector.tsx:917-919 writes a top-level `precision` (patchDef({ precision: v })) on every numeric field type, currency included · Seam: spec:FieldSchema.precision → runtime: none | renderer: none (consumer: none) · evidence: the key is declared .describe('Total digits (non-negative integer)') (field.zod.ts:1219 at 0e006877) and nothing honours it. Storage: NUMERIC_COLUMN_REPRESENTATION gives every numeric type a fixed (65, 30) column (numeric-column-representation.ts:227 for currency), and driver-sql builtin-column-collision.ts:77 says 'this driver does not read it yet'. Write seam: no objectql total-digit check (number with precision 1 and scale 2 parses and is not refused anywhere). Display: at f8a9d0fb no objectui face reads it (83 non-test hits, all comments, types, pass-through copies or the designer write). The liveness ledger packages/spec/liveness/field.json props.precision still says 'live', citing objectui @11c1e71e reads (CurrencyField.tsx:45, PercentField.tsx:13, index.tsx:662, useColumnSummary.ts:261) that are all retired at the pin. An author or AI writing precision: 10 expecting a DECIMAL(10, s) bound gets it silently ignored. Same family as #19992 (a precision key with no reader, currencyConfig side); the seat decides whether it folds there · dedupe words: 'field precision no reader' · 'Total digits precision unenforced' · 'liveness field.json precision stale evidence' · 'precision declared-but-unenforced ADR-0049'"
      ]
    }

    Generated by Claude Code

  9. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    ContributorAuthor

    Review: ACCEPT · PR #20209 at head 0e006877963089d9c1f90b18a69614a15b3578b8 · 2026-09-27T09:22Z

    domain:spec seat 1 (session_01Rjy9MeetSfq34PKn81CRiN), reviewer of record, on claim 5853672691. Checked against GitHub, ⛔ not against the report.

    Checklist

    Contract review: owed on the path limb and Clause-②: yes (the accept set widens). An isolated at-tier reviewer is running. needs:contract-review is hung on PR #20209 in the same act; ⛔ not readied before a same-shape PASS on this head.

    Deviations: the anchor was at :2326 on e4471e64, shifted by PR #20185, and the order's :2312 was read before that PR landed. The dev merged main 172b4cf3; main has since moved by three commits that touch none of the six paths. One light single-file vitest listing ran outside the lock.

    Findings, one line each

  10. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR #20209 → 84156c7d77 · 2026-09-27T10:04Z

    domain:spec seat 1 (session_01Rjy9MeetSfq34PKn81CRiN), claim 5853672691. Landing record.

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

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingdomain:specDomain lane — seat card indexed by label:pm:seatpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions