Repository navigation
[finding] FieldSchema.scale on a currency field is offered to authors by the field designer, ignored by every display face, and still enforced on writes — half-live in the direction that surprises #19629
Description
Activity
objectstack-fleet commented
on Sep 22, 2026 ContributorMore actionsTriage: moved to the decision box by the triage seat (
session_01Tw7jnJinGHvoGSi8aFkhPJ), 2026-09-22T18:45Z. ⛔ Not dispatchable until ruled.Path: records · 每种字段都渲染得出、收得下、存得住 | records-forms.field-type-constraints(② 步骤够不到) | P② | 待裁
Governing text: 维护者裁决 batch #194 item 1 letter A′;ADR-0049 enforce-or-remove;〈基本裁决原则〉「声明而未兑现是实现缺口,补实现或退役,⛔ 不在消费端收窄」
Prior rulings on this card: batch #194 item 1 A′ ordered this card to exist; ⛔ no ruling on the key's meaning维护者速读
货币字段有个「小数位」设置:设计器请作者填,显示的时候根本不看,但保存数据时又会按它把数据退回去。也就是说,作者被引导去填一个只会挡自己、不会改变任何显示效果的值。
两条路:让显示真的按它走(它从此有意义),或者把它从货币字段上彻底拿掉(设计器不再提供,保存也不再按它判)。
席位建议拿掉:它从来没生效过,所以没有人依赖它的效果;而留着它就等于一直在教 AI 和人去写一个系统不兑现的设置。
⚠️ 这和 #19628 是同一个设置的两个问题,建议一起裁,否则两张卡会给出互相打架的答案。要您定的一件事:A(让显示按它走)还是 B(货币字段上彻底拿掉它)?
The reading this rests on
在
currency(货币)字段上,scale这个设置是三面不一致的:- 设计器主动请作者填它 —— 对象字段检查器上就有这个输入,提交后写进字段定义(
ObjectFieldInspector.tsx:907-909,patchDef({ scale: v }))。这满足「(c) 元数据陷阱」的具名生产者要求:由写它的人以外的人存储并再编辑。 - 显示面不看它 —— 货币的渲染走另一条路。
- 写入校验却照样按它拒绝 ——
record-validator.ts:789-812:def.scale在number/currency/rating/slider上被直接当成允许的小数位。
⇒ 作者被引导去填一个「看不见效果、却会退回你数据」的值。
⚠️ 一个必须避开的坑(已实测):CurrencyConfigSchema上另有一个precision,而且它自己的别名表把scale映到precision(field.zod.ts:420),spec 在:1205-1206明确警告这是另一个面,⛔ 不要和FieldSchema.scale混为一谈。卡片的第二条腿指的是那个面。Options
A —— 对齐: 让货币的显示面也读
FieldSchema.scale。设计器的输入从此说话算话。
B —— 退役: 设计器不再在货币字段上提供scale,写入校验也不再对货币按它判。键在货币上彻底退休。四棱
- 实际业务需求:有具名生产者(设计器就在请作者填),所以这不是投机面;但⛔ 没有测到任何人依赖货币的
scale显示效果 —— 因为它从来没生效过。 - 项目长远合理性:「声明即强制」。今天是最糟的中间态:请人填、不兑现、还按它拒收。A 和 B 都终结这个中间态,A 让它有意义,B 让它消失。
- 防 AI 写错:这一轴指向 B 更强。AI 从设计器上看见这个输入,会把它写进元数据;写了之后行为不变,但写入会因它被拒 —— 这正是「声明一个运行时不兑现的能力」的教科书形状。B 让它当场不存在;A 让它生效,但也把一个新行为写进已发布面。
- 创业阶段不扩散:B 删一个入口,A 加一段显示逻辑。阶段姿态默认从紧,且废弃立即退休、不设过渡窗口。
席位推荐:B,并与 #19628 同批裁(同一个
scale的两个问题,分开裁会互相打架)。⚠️ 但两条路都改已发布的作者面:A 改显示行为,B 拿掉一个设计器入口并放宽一处写入校验 —— 放宽校验触安全/边界之外的「已发布契约语义」,按红线归维护者,⛔ 席位不代裁。
Generated by Claude Code
- 设计器主动请作者填它 —— 对象字段检查器上就有这个输入,提交后写进字段定义(
- addedarea:recordsBusiness objects, records, the views that show data, usable forms, searchBusiness objects, records, the views that show data, usable forms, search
on Sep 22, 2026 objectstack-fleet commented
on Sep 23, 2026 ContributorMore actionsRuling: batch #215 item 1 · letter B · maintainer 「215 同意」 2026-09-23T08:44Z
Director seat, summon #28 (
session_01GLdRPcbaCBQCTvVmU6YEUY). Presented in this seat's chat with recommendation B (retirescalefrom thecurrencyfield type;currencyConfig.precisionstays the one decimal-places knob for money); the maintainer approved the batch as presented. B′ (also enforce a declaredcurrencyConfig.precisionon writes) was offered as an optional add-on and ⛔ not taken —precisionis documented as a display width on both sides of the seam, so a write-side refusal would be a contract expansion, not this card's fix. A (align every money face toFieldSchema.scale) ⛔ not taken: it leaves two overlapping decimal-places knobs on one field type.Governing text: 〈基本裁决原则〉「声明而未兑现是实现缺口,补实现或退役,⛔ 不在消费端收窄」; ADR-0049 enforce-or-remove; batch #194 item 1 A′ (the absent-value ruling that ordered this card;
packages/spec/src/data/field-scale.tsdocblock); #7501 (an over-scale write is REFUSED, never rounded); #7918 / #11423 (CurrencyConfigSchema.precisionand its ISO contradiction check). Prior rulings read: fieldschema.scale,fieldschema,scale,currency,field,offered,authors,designer,ignored,display,face,enforced (+4 more) → 187 hits; ADR-0032 Decision §3, ADR-0005 Decision §5, ADR-0031 Decision §6, ADR-0031 Decision §7, ADR-0035 Decision §3, ADR-0041 Decision §4, ADR-0045 Decision §6, ADR-0055 Decision §1, ADR-0058 D3; thread: none; repo: objectstack-ai/objectstack — none rules on this key's meaning.Reading corrected on the card: 「ignored by every display face」 is false — the grid summary footer's currency arm reads
column.scale ?? 0(objectuipackages/plugin-grid/src/useColumnSummary.ts:343). Today's three faces on a currency field: cell → the currency's ISO minor units /currencyConfig.precision; summary footer →scale ?? 0; write validator →scale(packages/objectql/src/validation/record-validator.ts:789). Control reading:examples/**holds 2 currency fields, 0 declarescale.Ruled shape — B,
scaleis retired fromcurrencypackages/spec(FieldSchema):scaleon a field oftype: 'currency'is REFUSED at parse with a remedy namingcurrencyConfig.precision(the per-typesuperRefinehouse pattern). ⛔ No alias, ⛔ no grace window (阶段姿态: retirement is immediate). Thescaledescribe names the type set it still applies to. An ADR-0087 migration entry covers stored metadata (ascaleon a currency field → move the value tocurrencyConfig.precision, or delete it).packages/objectqlrecord validator: themax_scalebranch no longer readsdef.scaleforcurrency— the type leaves the enforced set;percent/number/rating/sliderare unchanged.- objectui (the consumer half, filed by this seat as currency fields: hide the
scaledesigner control; the summary footer and the metric tile take a currency's decimals from its ISO 4217 minor unit (objectstack#19910 ruling 乙) objectui#10221,pm:blockedon this card): the field designer stops offeringscaleon a currency field; the summary footer's currency arm derives fraction digits the way the cell does — ⛔ no?? N, ⛔ noscaleread on currency. - Pins: a currency field carrying
scalefails parse with the remedy; a currency write with more decimals than any declared precision is still ACCEPTED when nothing declares a width (today's contract, unchanged); the footer and the cell agree on a currency column.
Execution
needs-user-decision→pm:queuein this stroke;domain:spec·priority:p3·Clause-②: no(a narrowing). Samescalesurface as #19628 (ruled A′ in the same batch): two cards, two dispatches, one reading of the key.os-support-ai commented
on Sep 23, 2026 CollaboratorMore actionsClaim: PM loop — execute ruling B:
scaleis retired from thecurrencyfield type (refused at parse with a remedy namingcurrencyConfig.precision), and the record validator stops enforcingscaleon currency, dispatched at 2026-09-23T14:40Z
Session:session_013RDBh5DqXd2xnLwvHLgLFr
Branch:claude/issue-19629-currency-scale-retired
Worktree:objectstack-issue-19629
Domain:domain:spec
Seat:domain:spec#1
File surface:packages/spec/src/data/field.zod.ts(thecurrencyarm ofFieldSchema's per-typesuperRefine, and thescaledescribe) and its tests; one ADR-0087 semantic entry withpackages/spec/src/migrations/registry.tsregenerated, ⛔ never hand-edited; whatever the generators rewrite;.changeset/. Cross-lane, declared by the ruling's item 2:packages/objectql/src/validation/record-validator.ts(themax_scalebranch dropscurrencyfrom its enforced set;percent/number/rating/sliderunchanged) and its tests. ⛔ The objectui half is objectstack-ai/objectui#10221,pm:blockedon this card, and is not this claim.
Container & model:M,mode:subagent,model: opus (default judgment tier)
Clause-②: no
Thread-read: 5791803339
Serial constraints cleared: census over all 22 open PRs at 2026-09-23T14:40Z (254 file rows; lit control 55packages/spec/rows).field.zod.tsandrecord-validator.tsare CLEAR. PR #19728 editspackages/objectql/src/validation/rule-validator.ts, a different file.registry.tsis held by other open PRs, which is its normal state, resolved by the generator. #19628 is the linked card on the samescalekey (ruled A′ in the same batch). Linked cards are ⛔ never dispatched in the same batch, so it waits for this one.The ruling this executes:
5791803339(batch #215 item 1, letter B, 「215 同意」): ⛔ no alias, ⛔ no grace window; B′ (enforcingcurrencyConfig.precisionon writes) was ⛔ not taken.
Generated by Claude Code
objectstack-fleet commented
on Sep 23, 2026 ContributorMore actionsos-dev-report
{
"issue": 19629,
"status": "done",
"branch": "claude/issue-19629-currency-scale-retired",
"pr": "#19909",
"session": "session_013RDBh5DqXd2xnLwvHLgLFr",
"premise_still_valid": true,
"summary": "Executed ruling 5791803339, letter B. (1) FieldSchema refusesscaleon acurrencyfield at parse. It uses the per-type superRefine house pattern, raises one custom issue atscalewhose remedy namescurrencyConfig.precision, and has no alias and no grace window. Thescaledescribe names the set the key still applies to: number, percent, rating and slider (enforced on writes) and formula (rounded). (2) The record validator's max_scale branch skipscurrency; number, percent, rating and slider are unchanged, and min, max and the finite check still apply to currency. (3) ADR-0087 semantic entryfield-currency-scale-refusedwas added and registry.ts regenerated with gen:migration-registry. (4)scale: 2was deleted from 15 example Field.currency sites (app-crm 4, app-showcase 11) and from 13 documentation examples, plus the schema.mdx table row. The generated reference docs (field.mdx, object.mdx, migration.mdx) were regenerated. The changeset is minor for spec and objectql, with a BREAKING banner,Clause-②: no (narrowing)and the adr-0087registeredmarker. Premise measured on BASE 1f89ba0: a currency field withscale: 2parsed; a currency write of 1.234 withscale: 2was refused with max_scale {scale:2, actual:3}; max_scale readdef.scaleon all of number, percent, rating and slider (plus currency). FOUR READINGS DIFFER FROM THE RULING and are raised in open_questions, not acted on. (a)currencyConfig.precisionhas zero readers at.objectui-shapin 62597c588072 and in this repo's non-test code. The edit widget reads the field-levelprecision; the cell reads only the ISO minor unit. Adding a bare currencyConfig also materializes defaultCurrency 'CNY'. The remedy was written as ruled, but so that following it cannot add a currencyConfig. (b) The ruling's control '2 example currency fields, 0 declare scale' is wrong: 15 did. (c) A fourth face, the dashboard ObjectMetricWidget currency arm, readsscale ?? 0. (d) The validator half moves writes from refused to accepted, a direction the #19320 precedent declared as widening. Assignee was already set when I started (os-support-ai); I never wrote it. The newest Claim names this branch. One deviation: my first push was a baregit push, not wrapped in write-pace. The local permission classifier refused it before it reached the network. It was re-sent correctly through write-pace.",
"tests": "HEAD 7c0a33c. Every test run went through os-verify-lock. specvitest run --project local: 527 files, 15509 passed, 1 todo;--project repo: 35 files, 602 passed (both ran at a5858ed; the only later commit is a docblock-only line in field-scale.ts). objectql--project local: 305 files, 5093 passed;--project repo: 1 file, 5 passed. platform-objects: 54 files, 883 passed. metadata-core: 16 files, 285 passed. driver-sql: 179 files passed, 11 skipped; 2645 tests passed, 168 skipped. service-automation: 142 files, 1697 passed (includes the flow oracle). example-crm: 5 files, 45 passed. example-showcase: 29 files, 384 passed. typecheck exit 0 for spec, objectql, metadata-core, driver-sql, service-automation, example-crm and example-showcase. turbo build over packages and examples: 73 of 73 successful. Gates: dispatch-gates --commands derived 119 families; all 119 were run on 7c0a33c with exit codes captured before any pipe, and all exited 0.--ranreconciliation: '119 derived famil(ies) accounted for — 119 run, 0 NOT-MEASURED (a DERIVED zero — all 119 recorded an exit code and none of them is 3)'. Three families first answered PREREQUISITE NOT MET (exit 3) on a partial build and were re-run after the full build: dual-build-cjs-loads, type-check-debt, skill-examples. check:objectui-pin-citations went red once on my own new docblock; it was fixed in 7c0a33c. check:generated: 'All 15 generated artifacts are up to date'. check-adr-0087-registration: '[BREAKING+bang+clause-②-narrowing] registered field-currency-scale-refused'. check-changeset-no-major: no major; its level axis reads the PR body and was NOT MEASURED locally. Lint was narrowed to the diff: eslint --no-inline-config --format json over the 24 changed ts/mjs files counted 24 files, 0 errors, 0 warnings and no ignore notices. eslint.config.mjs never enables type-aware linting (its comment at lines 326-328), so no untouched file's verdict can move; the full pnpm lint belongs to CI. Reverse verification ran from the committed state with a trap on EXIT/INT/TERM. The file was restored to its BASE blob and the landing was proved by hash and by an anchor count of 0, then the file was restored withgit checkout HEAD --and the restore was proved by hash equality with the HEAD blob and an emptygit diff HEAD. Both subjects resolve through a relative src import, so no rebuild or dist preflight was involved. field.zod.ts at BASE blob cc96254284 gave 5 failed, 3 passed in field-currency-scale-refused.test.ts: the four refusal pins and the describe pin went red and the three controls stayed green; restored to 0c24baa382. record-validator.ts at BASE blob 2e2d3981d2 gave 1 failed, 111 passed in record-validator.test.ts: only the legacy-scale-accepted pin went red, while the unchanged-contract pin, the min/max/finite pin and the number/percent controls stayed green; restored to c200751358.",
"mcp_calls": "0",
"api_writes": "7 attempted writes, 6 landed. git push through write-pace: 5 attempts, 4 landed (the empty-branch probe, 2dbab9f, a5858ed on a retry after one remote HTTP 500, and 7c0a33c). fleet-write relay: 1 POST /repos/objectstack-ai/objectstack/dispatches (HTTP 204, run 35894914237 success), which executed op pr_create as POST /repos/objectstack-ai/objectstack/pulls with draft true and opened #19909. This os-dev-report comment is 1 POST /repos//issues/19629/comments via post-stamped. 0 label writes: the dispatch forbids them. 0 PATCH writes to the PR body. Denied before the network: 1 bare git push, refused by the local classifier.",
"open_questions": [
{
"question": "The ruled remedy namescurrencyConfig.precision, but at.objectui-shapin 62597c588072 nothing reads that key. The cell uses only the currency's ISO 4217 minor unit. The edit widget, packages/fields/src/widgets/CurrencyField.tsx:76-77, reads the FIELD-levelprecision, and its own comment sayscurrencyConfig.precisionis a different key it never reads. This repo's liveness ledger agrees. The ruling's reading 'cell reads ISO minor units / currencyConfig.precision' therefore does not hold. Also, adding a currencyConfig block materializes defaultCurrency 'CNY', so a naive move changes the currency the field displays. Which key, if any, should the refusal and the migration entry point to?",
"options": [
"A: keep as ruled and shipped. The remedy says delete by default, and move to currencyConfig.precision only on a field that already declares a currencyConfig. Cost: the remedy still names a key no face reads.",
"B: name the field-levelprecision, which the pinned edit widget reads as display width and which #7918 already checks against a fixed currency. Cost: its describe says 'Total digits', which contradicts that reading (see findings), and the cell ignores it. Replacement sentence for the refusal and the entry: 'Delete the key. To pin the amount's display width, declare the field-levelprecisioninstead.'",
"C: name no knob. Replacement: 'Delete the key: a currency amount's decimal places come from its currency (the ISO 4217 minor unit).' Also drop the move arm from the migration entry. Cost: departs from the ruling's letter."
],
"recommendation": "C, or A until the seat rules. C removes the one remaining pointer to an unread key, and every one of the 15 in-tree sites was a delete anyway. The four-axis framework was not carried in the dispatch, so this recommendation is not axis-scored. Changing the remedy means re-running the parse-refusal pin's named-subject assertion (field-currency-scale-refused.test.ts, first test)."
},
{
"question": "Landing order. At the pin, the grid summary footer and the dashboard ObjectMetricWidget currency arm readscale ?? 0. Withscale: 2deleted from the 15 example fields, those two faces render currency with zero decimals from this merge until objectui#10221 lands with a pin bump. The metric widget is not in objectui#10221's ruled scope.",
"options": [
"A: land this PR as is and accept the transitional window.",
"B: hold this PR's merge until objectui#10221, widened to cover the metric widget, lands and the pin moves.",
"C: unblock objectui#10221 now. Deriving the footer and metric digits from the currency does not depend on this spec change, so it can land first."
],
"recommendation": "C, then land this. The consumer half has no build dependency on the spec narrowing, and landing it first closes the zero-decimal window."
},
{
"question": "Clause-② direction. The ruling and the Claim sayno, and the PR and changeset declareClause-②: no (narrowing). But the validator half moves writes from refused to accepted, for currency defs that still carryscale. The #19320 precedent declared a max_scale allowance change of that direction asyes (widening).",
"options": [
"A: keepno (narrowing)as ruled. After this change no authored currency field can declarescale, so the widened cell set is stored or hand-built defs only.",
"B: re-declareyes (narrowing)in both the PR body and the changeset."
],
"recommendation": "A. It is the ruling, and the widening reaches only defs the spec now refuses. It is flagged because the precedent reads the other way."
}
],
"out_of_scope_findings": [
"class: c · Seam: spec:CurrencyConfigSchema.precision → renderer: none (consumer: none at.objectui-shapin 62597c588072; objectstack non-test code: none). The key is taught as a currency width by content/docs/data-modeling/fields.mdx (currencyConfig example with precision 2) and now by this PR's ruled remedy. Every face silently drops it: the cell renders the ISO minor unit, and the widget reads the field-level precision (CurrencyField.tsx:76-77, whose comment says 'a different key on the currencyConfig block that this widget never reads'). Named producer: authored metadata and the docs example. · dedupe words: currencyConfig precision unread · currency precision no reader · CurrencyField field precision · currencyConfig precision display width",
"class: b · Seam: spec:FieldSchema.precision → renderer:CurrencyField. The contract text saysprecisionis 'Total digits (non-negative integer)' (field.zod.ts describe; schema.mdx table 'Total digits (including scale)'). But FieldSchema's own #7918 superRefine judges a currency field'sprecisionas FRACTION digits against the ISO 4217 count, and the pinned CurrencyField renders it as fraction digits. So the documentation YAML examples withprecision: 18on a currency field (schema.mdx ×3, types.mdx) would render 18 fraction digits in the edit widget. This predates this PR; those lines were not touched. · dedupe words: precision total digits currency · currency precision 18 docs · field precision fraction digits currency · #7918 field precision",
"class: b · Seam: spec:FieldSchema.scale (retired on currency) → renderer:ObjectMetricWidget. The pinned console's plugin-dashboard/src/ObjectMetricWidget.tsx:229 currency arm readsvalueFieldDef.scale ?? 0. After this PR it is a reader of a key the spec now refuses on currency, and it reaches no row in the ruling's face list. Carrier: objectui#10221 (the ruling's consumer half); I name it as a sub-issue or scope extension of that card, not a new card. · dedupe words: metric widget currency scale · ObjectMetricWidget scale currency · dashboard metric decimals currency",
"carrier: checklist-author seat (承接者:无 named) · noted, not filed. docs/qa/platform-checklist/areas/records-forms.json still describes f_currency as 'scale 2 currencyConfig{precision 2}', and its knownGaps text ('scale not enforced on write') has been stale since #7501. Recorded in the PR's Acceptance notes.",
"carrier: none (承接者:无) · noted, not filed. The FieldSchema alias table renamesdecimals/decimalPlacestoscale, so on a currency field that rename now leads into this refusal. The refusal's remedy carries the author on, so this is not a defect. Recorded in Acceptance notes.",
"carrier: none (承接者:无) · noted, not filed. hotcrm (hotcrm#1206, cited by the flow oracle) declaresscale: 2on a currency quote field, which becomes a refused parse downstream. The migration entry covers it. Recorded in Acceptance notes."
]
}os-support-ai commented
on Sep 23, 2026 CollaboratorMore actionsHold note, 2026-09-23T17:25Z: PR #19909 executes ruling
5791803339to the letter. The round measured counter-facts:currencyConfig.precisionhas no reader at the objectui pin or in this repo's non-test code, and two console faces (the grid summary footer andObjectMetricWidget) readscale ?? 0, so deletingscalefrom the 15 example fields shows those amounts with zero decimals until objectui#10221 lands. Filed as decision card #19910. ⛔ The seat does not arm PR #19909 for merge until #19910 is answered.
Generated by Claude Code
objectstack-fleet commented
on Sep 24, 2026 ContributorMore actionsRuling correction: batch #215 item 1 · letter B unchanged · one control reading corrected · director seat 2026-09-24T00:33Z
Director seat, summon #28 (续) (
session_01GLdRPcbaCBQCTvVmU6YEUY). Ruling 5791803339 said 「examples/**holds 2 currency fields, 0 declarescale」. That reading was WRONG: the instrument matchedtype: 'currency'literals and missed theField.currency(…)builder. Re-taken onorigin/mainfdeeea0: 16Field.currency(sites inexamples/**, 15 of them declarescale: 2— as the #19629 round found and PR #19909 deletes under the ruling's migration prescription. The letter (B) stands. The counter-facts that round measured aboutcurrencyConfig.precision(no reader anywhere) are the subject of #19910, presented to the maintainer as batch #218 item 2; ⛔ nothing is re-ruled here.10 remaining items
objectstack-fleet commented
on Sep 24, 2026 ContributorMore actionsBlocker re-derived: objectui#10221 landed; the card now waits on the console pin bump #20029
Blocked-by: #20029
Unlock-action: re-check PR #19909domain:specexecution seat 1 (session_013RDBh5DqXd2xnLwvHLgLFr), 2026-09-24T20:14Z. The upstreamobjectstack-ai/objectui#10221closed, so per comment5818216091the blocker is re-derived rather than released.- The upstream closed. objectui#10221 closed
completedat 2026-09-24T20:04Z. It landed as objectui PR [finding] Three spellings of the ADR-0068 platform-admin read now live in plugin-auth, and one of them skips the system read context #10348, merge commit0651e7ab4; see thedomain:uiseat 4's note5821464869on PR fix(spec,objectql)!: retirescalefrom the currency field type — refused at parse, no longer enforced on writes #19909. - The pin does not cover it yet.
.objectui-shaonorigin/mainis62597c588072, and RESTcompare 62597c588072...0651e7ab4answersaheadby 78. Ruling 乙 item 2 holds PR fix(spec,objectql)!: retirescalefrom the currency field type — refused at parse, no longer enforced on writes #19909 until the pin moves. - The next blocker is the bump. No bump card or PR was open (dedupe in Bump .objectui-sha past objectui 0651e7ab4 (objectui#10221, the currency summary footer and metric tile) — PR #19909 lands only after the console pin moves #20029's body: 5 title hits, 0 open bump cards), so Bump .objectui-sha past objectui 0651e7ab4 (objectui#10221, the currency summary footer and metric tile) — PR #19909 lands only after the console pin moves #20029 is filed as that bump. The
Blocked-by:line above replaces the one in5818216091. - State is unchanged. The card stays
pm:blocked; the claim and assignee stay. PR fix(spec,objectql)!: retirescalefrom the currency field type — refused at parse, no longer enforced on writes #19909 atd7740c324estill owes its at-tier re-review, andneeds:contract-reviewis on it.
- The upstream closed. objectui#10221 closed
objectstack-fleet commented
on Sep 25, 2026 ContributorMore actionsUnlocked: #20029 closed and the pin covers objectui#10221's fix; PR #19909 goes to the queue
domain:specexecution seat 1 (session_013RDBh5DqXd2xnLwvHLgLFr), 2026-09-25T00:38Z, running its 留守 item as named in the shift brief5824072704on #6017. TheBlocked-by: #20029line in5821531422was re-derived and no new blocker was found, so this act replacespm:blockedwithpm:dispatched. The claim and the assignee stay until the PR merges.- The upstream is closed. Bump .objectui-sha past objectui 0651e7ab4 (objectui#10221, the currency summary footer and metric tile) — PR #19909 lands only after the console pin moves #20029 closed
completedat 2026-09-25T00:24Z. The bump landed as PR chore(objectui): bump the console pin to f8a9d0fb0596 (carries objectui#10221 and the objectui#9910 re-cut) with re-measured pin citations and the lockstep re-record #20036 (0bf85eaae6), which moved.objectui-shatof8a9d0fb0596f4521076628e2bbfe27e6ce67d52. - The pin covers the fix. REST
compare f8a9d0fb0596...0651e7ab4on objectui answersbehindby 14, ahead by 0, so0651e7ab4(objectui#10221, merged as objectui PR [finding] Three spellings of the ADR-0068 platform-admin read now live in plugin-auth, and one of them skips the system read context #10348) is an ancestor of the pin. Ruling 乙 item 2's order is met: objectui#10221 first, then the pin, then PR fix(spec,objectql)!: retirescalefrom the currency field type — refused at parse, no longer enforced on writes #19909. - The PR is ready to land.
- Its head is still
d7740c324e92789285785ed3632148d078e6b8cf, the head that at-tier PASS5823741549reviewed. CI reads 31 success, 4 skipped, 0 failure, and the PR ismergeable: true/clean. check-governed-merges.mjsanswered NOT governed (0 of 37 paths, 636 lines).- There is no base merge: the head does not move, so the PASS still holds. The merge queue measures the change against current
main.
- Its head is still
- Next:
ccr/ready_for_review, thenccr/auto_mergesquash, then the queue. After the merge, the change is verified by content onorigin/main, with the landing record here.
- The upstream is closed. Bump .objectui-sha past objectui 0651e7ab4 (objectui#10221, the currency summary footer and metric tile) — PR #19909 lands only after the console pin moves #20029 closed
objectstack-fleet commented
on Sep 25, 2026 ContributorMore actionsLanded — PR #19909 →
5b9402d89bonorigin/main, verified by contentdomain:specexecution seat 1 (session_013RDBh5DqXd2xnLwvHLgLFr), 2026-09-25T01:02Z. This is the 留守 item named in the shift brief5824072704on #6017.Path.
- Rulings executed. Ruling B
5791803339, reworded by ruling 乙5805782503. - At-tier review. The round-3 record
5823741549(director seat, summon chore(deps)(deps-dev): bump @vitest/coverage-v8 from 2.1.9 to 4.0.17 #29) reads VERDICT: PASS on headd7740c324e, and that head is what landed. It followed the FAIL5818558748on40ef043003. - Governed check.
check-governed-merges.mjsanswered NOT governed: 0 of 37 paths hit the register, 636 changed lines. - Landing order. Ruling 乙 item 2's order held:
- objectui#10221 landed as
0651e7ab4. - The pin moved past it in PR chore(objectui): bump the console pin to f8a9d0fb0596 (carries objectui#10221 and the objectui#9910 re-cut) with re-measured pin citations and the lockstep re-record #20036 (
0bf85eaae6→.objectui-shaf8a9d0fb0596; RESTcomparepin...0651e7ab4=behind14). - Then this PR landed.
- objectui#10221 landed as
- Queue and merge. Seat 1 flipped the PR to ready and enabled
ccr/auto_mergeat 2026-09-25T00:38Z. It joined the queue at 2026-09-25T00:39Z and merged at 2026-09-25T01:00Z as squash5b9402d89bb1d9d5c5ac6620e5d199da3851d7c7, with parent3557f85fa5.
Re-measured on
origin/main, parent against squash:reading parent squash field.zod.ts:if (field.type === 'currency' && field.scale !== undefined)(the refusal)0 1 field.zod.ts: the ruled first sentence "scaleis not valid on acurrencyfield — delete the key."0 1 field.zod.ts: the describe clause "REFUSED on acurrencyfield — delete it there"0 1 the refusal block itself (13 lines) mentioning currencyConfigorprecision(ruling 乙 ⛔)— 0 18.field-currency-scale-refused.tsexists / itscurrencyConfigmentions0 / — 1 / 0 registry.ts:'field-currency-scale-refused'0 1 record-validator.ts:t !== 'currency' &&(currency leaves themax_scaleset)0 1 object.form.ts: thescalerow offered on['number','currency','percent'](round-3 fix)1 0 object.form.ts: thescalerow offered on['number','percent']0 1 examples/**:Field.currency(lines that carryscale: 28 0 dark control: object.form.ts'sprecisionrow (#7918), unchanged1 1 dark control: field.zod.ts's pre-existingcurrencyConfig.precisionmention (a comment at:486, outside this PR)1 1 Card state.
pm:dispatchedis removed and the assignee is cleared in this act (relay run36080204764, read back as 3 labels and 0 assignees).- GitHub closed the card
completedthroughFixes #19629. - The 留守 Routine
trig_01FU95dKzWatpxgAxF9m18w2is deleted after this record.
- Rulings executed. Ruling B
- added 4 commits that reference this issue
on Sep 28, 2026 - added a commit that references this issue
on Sep 30, 2026
Ruled: 5791803339 · letter B · 2026-09-23T08:49Z
Filed at 2026-09-22T02:11Z by the
domain:specseat 4 (session_01AmH9bKvGoLjiY86Q4Z3og2, seat post #18917), from theout_of_scope_findingsof the #19579 round, marked class (c) by the dev. ⛔ Filed unassigned, ⛔ nopriority:*, ⛔ no type — routing and grading are triage's. ⛔ Not a claim. ⛔ Not a ruling.⭐ This card exists because a ruling said it must. Ruling batch #194 item 1 letter A′ scoped
numberandcurrencyto ride #19579's PR only if a consumer census showed one consistent absent-value per type, and「where consumers disagree, that type gets its own card with the readings」. The
currencycensus came backinconsistent. ⇒ this is that card, with those readings.
The defect, in one sentence
FieldSchema.scaleon acurrencyfield is offered to authors by a named producer, ignored by everydisplay face, and still enforced on writes — so setting it buys a narrower write contract and no visible
change. Half-live, in the direction that surprises rather than refuses.
⏱️ Readings — attributed, ⛔ NOT re-taken by this seat
origin/main0cf2d6644band re-read identicallyat the pinned
.objectui-sha87af769e9a. This seat has ⛔ not re-driven them and does ⛔ not assertthem; it files them because the ruling instructs it to and because a card with the readings is worth more
than a card that asks someone to find them again. Whoever takes this re-measures leg 1 first — a producer
is the difference between a dead key and a trap.
Leg 1 — the producer is named, and it is stored metadata, ⛔ not a React prop. The metadata-admin field
designer ships a
scalecontrol:packages/app-shell/src/views/metadata-admin/inspectors/ObjectFieldInspector.tsx:907-909—label={tr('designer.field.scale')},onCommit={(v) => patchDef({ scale: v })}, with the label declared ini18n.ts:1477/:3448. ⇒ an author is invited to set it, in a UI this platform ships, and the value isre-authorable stored metadata.
Leg 2 — no display face reads it on a currency field.
CurrencyCellRendererformats throughformatCurrency(value, currency, locale)and passes no scale;packages/fields/src/widgets/CurrencyField.tsxreads
precisionand says so in-line (「Noscalepassed: this is a currency widget whoseprecisionis a …」);fraction digits otherwise come from the currency's own ISO 4217 minor-unit count.
Leg 3 — the write path DOES enforce it.
packages/objectql/src/validation/record-validator.ts:789-812enforces
max_scaleon a currency write from that same declaration. ⭐ This seat did verify the gate'sshape first-hand on
origin/mainwhile reviewing #19624, quoted:⇒ the enforcement is real and is gated on the declaration being present.
⇒ The observable consequence
An author sets
scale: 3on a currency field in the designer and gets:declaration is enforced, just not where the author was looking.
Why a guessed default is refused here
The ruling names it: a global
0would turn an undeclared currency from$25.00into$25, which isexactly why plain letter A was refused. ⇒ ⛔ this card does not propose a default, and the shape of
the answer is open — align the display faces to the declaration, retire the key from the currency surface, or
split display width from write allowance. ⛔ This card states no preference.
Seam
spec:FieldSchema.scale→producer:ObjectFieldInspector|renderer:CurrencyCellRenderer|renderer:CurrencyField|objectql:record-validator max_scaleWhy it lands in THIS repo
The ruling puts the declaration in
packages/spec; the consumer half follows in objectui. 〈多仓协调〉rule 1:「issue 住在修复落地的仓」.
⛔ Independent card, ⛔ NOT a sub-issue of #19579 and ⛔ not blocked by it.⚠️ Related but ⛔ not a
duplicate: the sibling card filed in the same act covers
number, whose obstruction is different (adeclared 0 also turns grouping off).
Dedupe
currency scale ignored display·currency precision vs scale·formatCurrency ISO 4217 minor units·CurrencyField precision·max_scale currency write·designer offers scale currency