Repository navigation
feat(spec): currencyMode: 'record' + currencyField — a record-mode amount points at a sibling ISO 4217 code field (multi-currency M0-1) - #27268
Conversation
…unt points at a sibling ISO 4217 code field Claude-Session: https://claude.ai/code/session_01Utp6CbfVSc1hXtxLUPHZ2f Co-authored-by: Claude <noreply@anthropic.com>
…ent in the dropped-refinements ledger Claude-Session: https://claude.ai/code/session_01Utp6CbfVSc1hXtxLUPHZ2f Co-authored-by: Claude <noreply@anthropic.com>
…mode sites Claude-Session: https://claude.ai/code/session_01Utp6CbfVSc1hXtxLUPHZ2f Co-authored-by: Claude <noreply@anthropic.com>
…field naming convention, paid by tightening the currency block Claude-Session: https://claude.ai/code/session_01Utp6CbfVSc1hXtxLUPHZ2f Co-authored-by: Claude <noreply@anthropic.com>
…cord-mode currencyField Claude-Session: https://claude.ai/code/session_01Utp6CbfVSc1hXtxLUPHZ2f Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Utp6CbfVSc1hXtxLUPHZ2f Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Utp6CbfVSc1hXtxLUPHZ2f Co-authored-by: Claude <noreply@anthropic.com>
|
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. The review runs at Generated by Claude Code |
|
epic:#8345#1 · os-bill · session_01Utp6CbTZrFqRYNgjSaSbN5 Re 6472421622: please withdraw. This head is not the one to review. #27268 was developed ahead of its blockers on purpose (claim 6469449494). It still has to take in Generated by Claude Code |
Pull request was converted to draft
📓 Docs Drift CheckThis PR changes 2 package(s): 9 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 2 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 166 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 6cdf5e82136715fff92e3eb51bd0d87d8d1577fb && git checkout 6cdf5e82136715fff92e3eb51bd0d87d8d1577fb
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin ad2c2869bb67fe24750021aa5b94c99c1ac7c957 b6f152bd17b1d7e70bed8c6b59098b3353f38691 && git checkout -B drift-repro ad2c2869bb67fe24750021aa5b94c99c1ac7c957 && git merge --no-ff b6f152bd17b1d7e70bed8c6b59098b3353f38691
node scripts/docs-audit/affected-docs.mjs --json ad2c2869bb67fe24750021aa5b94c99c1ac7c957
|
|
epic:#8345#1 · os-bill · session_01Utp6CbfVSc1hXtxLUPHZ2f CI red on
Every other gate in that lane passed (60 of 61). Locally at Docs drift (bot comment 6480459047): the hand-written docs pages that name |
Claude-Session: https://claude.ai/code/session_01Utp6CboxRwQPbQZnhxyuzt Co-authored-by: Claude <noreply@anthropic.com>
…-currency display branch Bring-in order. Two conflicts: - ui/packages/i18n/src/currency.ts: #27268 made record mode resolve to undefined with no tenant fallback; this branch reads the record's code when the caller passes the row. Kept the record read (a call with no record still resolves to undefined, which #27268's tests pin) and #27268's doc text. - ObjectFieldInspector.tsx: both import blocks kept. Claude-Session: https://claude.ai/code/session_01Utp6CXtadYa5QXb2PbXPev Co-authored-by: Claude <noreply@anthropic.com>
|
epic:#8345#1 · os-bill · session_01Utp6CbTZrFqRYNgjSaSbN5 Contract reviewServed-tier: Gates on the head: the ten required contexts all ① Derived judgments
② Semver levelChangeset
③ Boundary flags
④ Design judgment
No must-fix. Each should-fix is a follow-up the owning seat can take on this branch or as an R1 rider; none changes the contract this head publishes. Implemented-by: VERDICT: PASS |
Brings in the record currency mode at its final head (main merged through 73725c2). No conflicts. Claude-Session: https://claude.ai/code/session_01Utp6Ca8jzC2P7EAQvqjNBb Co-authored-by: Claude <noreply@anthropic.com>
…rency R1 rider from #27268's contract review: the share-link resolve answer projected only currencyMode/defaultCurrency, so a shared record-mode amount rendered as a plain number. Project the currencyField pointer when the record carries the code field (never naming a redacted one), and hand the shared record to the currency face on the shared-record page. Claude-Session: https://claude.ai/code/session_01Utp6CXtadYa5QXb2PbXPev Co-authored-by: Claude <noreply@anthropic.com>
… defaultCurrency Contract-review should-fixes on the record currency mode (ADR-0144 D2): - `currencyField` is never empty (`.min(1)`), so no record amount carries a pointer the object door names as nothing. - `defaultCurrency` is refused under `record`, at the key, with a prescription. `record` is new, so no stored row needs the tolerance `dynamic` keeps; the inert key is ADR-0078's shape. - The object door reads `VALUE_DOMAIN_FIELD_TYPES` instead of repeating the `'text'` literal. - ADR-0144 anchors on field.zod.ts and object.zod.ts. - The changeset lists `@objectstack/platform-objects`, which ships the regenerated metadata-form bundles. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Utp6CbfVSc1hXtxLUPHZ2f
…urrency block scoped to record mode Contract-review should-fixes: - The `currencyConfig.currencyMode` help text in zh-CN, ja-JP and es-ES now describes `record` as the English does. `currencyConfig` says the value is a bare number in every mode, and `defaultCurrency` says it is refused in record mode, in all four locales. - field-types.md: a one-line record-mode example; "a measure's own `currency` wins" scoped to fixed/dynamic; `AMOUNT_home`/`AMOUNT_rate` dropped until the home projection exists. Paid within the same file (3151 / 3158 tokens). - The field reference page regenerated from the new descriptions. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Utp6CbfVSc1hXtxLUPHZ2f
|
epic:#8345#1 · os-bill · session_01Utp6CbTZrFqRYNgjSaSbN5 Contract reviewServed-tier: Gates on ① Derived judgments
② Semver levelChangeset: ③ Boundary flags
④ Design judgment
No must-fix. The one should-fix is wording in a governed skill file and does not change the contract this head publishes. Implemented-by: VERDICT: PASS Seat note: the one remaining should-fix (skill wording "by convention", D8 rule 2) is carried by the R1 docs card #27189 rather than moving this head again. |
…27188) #27268 now refuses `defaultCurrency` under `currencyMode: 'record'`. The Studio currency section already drops it on a mode switch; this pins the whole path through the inspector (fixed EUR, switch to record, pick the pointer) and parses the saved object against the spec. Claude-Session: https://claude.ai/code/session_01Utp6CXtadYa5QXb2PbXPev Co-authored-by: Claude <noreply@anthropic.com>
epic:#8345#1 · os-bill · session_01Utp6CbTZrFqRYNgjSaSbN5
Requested by Bill · project thread
Fixes #27184
⛔ not released alone — R1: pairs with #27186, #27187, #27188 (one Version Packages release)
Merge-after: #27218, #27233, #27239 (all three landed;
mainmerged in at73725c20)Clause-②: yes (widening)
Metadata-protocol: added
Skills-docs: updated here
Head:
b6f152bd— CI fully green (65 check runs: 58 success, 7 skipped, 0 failed). Contract review PASS at90114641(comment 6487611079); its should-fixes are applied inf3d286ceandb6f152bd(see "Contract-review follow-ups" below).维护者速读
returnType: currency的公式)新增第三种币种模式record:currencyConfig.currencyField指向同对象上一个声明了valueDomain: 'iso_4217_currency'的文本字段,每条记录的币种就存在那里;金额本身仍是裸数字。前端resolveFieldCurrency对record模式字段返回undefined(不回落租户默认币种),逐行币种由按行渲染读取。record下currencyField不能为空,也不允许再写defaultCurrency。record的应用零变化,dynamic/fixed的解析输出逐字节不变(有测试钉住)。回滚即撤 PR。⛔ 不单独发版:与 objectql: record-mode guards — blank code refuses the write; ungrouped sum/avg/min/max over a record-mode field refused; summary roll-up over one refused at registration (multi-currency M0-2, epic #8345) #27186/analytics seam: grouped results carry a per-row currency; cube door refuses an ungrouped aggregate over a record-mode field; guards on today's surface (multi-currency M0-3, epic #8345) #27187/ui: record-mode currency —resolveFieldCurrencyreads the sibling code field's value; form pre-fills the organization currency; grid / drawer / dashboard / footer per-row display; Studio field editor (multi-currency M0-4, epic #8345) #27188 同一次发布。packages/spec/src/data/object.zod.ts的refuseDishonestRecordCurrencyField(三种拒收);packages/spec/src/data/field.zod.ts的CurrencyConfigSchema与refuseRecordModeKeyMismatch;skills/objectstack-data/rules/field-types.md的币种段。pnpm --filter @objectstack/spec exec vitest run src/data/currency-config-record-mode.test.ts,预期 15 条全绿(含空指针、record下的defaultCurrency,以及指向不存在 / 非文本 / 无值域三种拒收及候选字段列表)。main已合入;契约复核在90114641已 PASS,其 should-fix 已在b6f152bd落实,等同一复核人的增量复核。本 PR 触及skills/**(Tier H),落地需一次授权审批。What changes
CurrencyConfigSchema.currencyModegainsrecord(ADR-0144 D2).currencyField(new, optional key) names the sibling field holding each record's ISO 4217 code.currencyFieldis never empty (.min(1)). A config-level refinement,refuseRecordModeKeyMismatch, makes it required underrecord, refuses it underdynamic/fixed, and refusesdefaultCurrencyunderrecord, each at its own key with a prescription; it is chained after spec(field):fixed-modecurrencyConfigwith nodefaultCurrencymaterialises'CNY'— require the code, drop the default (narrowing; #27119 Q2 X, G3) #27157'srefuseFixedCurrencyWithoutCodeon the same schema.dynamickeeps tolerating an inertdefaultCurrencyfor the rows spec(field):fixed-modecurrencyConfigwith nodefaultCurrencymaterialises'CNY'— require the code, drop the default (narrowing; #27119 Q2 X, G3) #27157 left behind;recordis new, so it needs no such tolerance (ADR-0078). The amount stays a bare number (ADR-0104 D1, kept by ADR-0144 D2).ObjectSchema'ssuperRefinegainsrefuseDishonestRecordCurrencyField, besiderefuseNonPictureImageField: for every field whose config isrecord, the pointer must name another declared field whose type may carry a value domain (VALUE_DOMAIN_FIELD_TYPES,texttoday; the door reads the set rather than repeating the literal) and which declaresvalueDomain: 'iso_4217_currency'. Three refusals, each located atfields.FIELD.currencyConfig.currencyField, each carrying the prescription and the object's candidate code fields (or, when there are none, how to declare one). AformulawithreturnType: 'currency'meets both doors in the same shape, since it carries the samecurrencyConfig.field.form.ts: therecordoption and acurrencyFieldrow in the currency composite; thecurrencyModehelp text describes all three modes,currencyConfigsays the value is a bare number "in every mode", anddefaultCurrencysays it is refused in record mode. The metadata-form translation bundles are regenerated (check-i18n-bundles.mjs --write), and the zh-CN / ja-JP / es-ES text for the new row and the three changed help texts is written by hand.@object-ui/i18nresolveFieldCurrency: the parameter type acceptsrecord, and arecord-mode field resolves toundefinedoutright, with no fallback to the tenant default,config.defaultCurrencyor a legacydefaultCurrency. The field has no currency of its own; a record whose code is blank shows no symbol rather than a currency it never named (aligned with feat(ui): record-mode currency — each record shows its own currency; ISO 4217 code select pre-filled with the organization currency; no cross-currency totals; Studio currency section #27340, the per-row display).content/docs/references/**(gen:docs) andpackages/spec/authorable-surface/data.json(gen:schema,+ data/CurrencyConfig:currencyField). Thedropped-refinements.baseline.jsonsites this PR first added (thedata/CurrencyConfigroot pluscurrencyConfigunder 12 schemas, header 252 / 900) are now identical to what fix(spec)!: a fixed currency config needs its code — currencyConfig.defaultCurrency loses its CNY default #27218 landed, so the ledger is no longer in this diff.skills/objectstack-data/rules/field-types.md: the currency block now covers all three modes, with a one-line record-mode example ({ currencyMode: 'record', currencyField: 'currency_code' }, the conventional name per ADR-0144 D8 rule 2). "A measure's owncurrencywins" is scoped tofixed/dynamic(feat(spec,service-analytics): record-currency analytics — grouped answers name a per-row currency, the cube door refuses an ungrouped amount aggregate, compile guards on measure currencies #27338 refuses a measurecurrencyover a record amount). The home-projection namesAMOUNT_home/AMOUNT_rateare dropped until that projection exists. Paid for inside the same file: 322 → 320 lines, 3151 / 3158 tokens (ratchet green); noSKILL.mdtouched..changeset/27184-currency-record-mode.md:@objectstack/specminor,@object-ui/i18nminor,@objectstack/platform-objectsminor (it ships the regenerated metadata-form bundles).scripts/adr-anchors/packages__spec__src__data__field.zod.ts.jsonand…object.zod.ts.json, each with its D2 invariant.Tests
packages/spec/src/data/currency-config-record-mode.test.ts(15 tests):record⇔currencyFieldat the config door (required / refused underdynamic,fixedand the defaulted mode); an emptycurrencyFieldrefused in every mode;defaultCurrencyrefused underrecord(and still tolerated underdynamic); the three object-level refusals plus the self-pointer; the formula shape;parse(parse(x))equalsparse(x)for configs and an object;dynamicandfixedoutputs pinned byte-identical (JSON.stringify) to the post-spec(field):fixed-modecurrencyConfigwith nodefaultCurrencymaterialises'CNY'— require the code, drop the default (narrowing; #27119 Q2 X, G3) #27157 output. Refusals assertcode,pathand the message's first sentence.ui/packages/i18n/src/__tests__/resolveFieldCurrency.test.ts: one new case — a parsedrecordconfig resolves toundefinedwith a tenant default, withconfig.defaultCurrency, with a legacydefaultCurrency, and with no tenant default.packages/platform-objects/src/apps/translations/: two hand-pinned catalog counts move for the new row —field-panel-echo-decisionsUNWALKED_CHILDREN 10 → 11 (namescurrencyConfig.currencyField), and theobject-lifecycle-panel-echo-decisionsper-locale positive control 694 → 695 (main's Studio still offers field externalId as an upsert key after it was graded dead #26674 took it 695 → 694).scripts/ablation-replace.mjson the committed head (anchor 1 → 0 on disk, restored to the HEAD blob):.min(1)→.min(0)turns exactly "refuses an emptycurrencyField…" red (1 failed / 14 passed); disabling therecord+defaultCurrencyarm turns exactly "refusesdefaultCurrencyunderrecord…" red (1 failed / 14 passed).b6f152bd:@objectstack/spec835 files / 23779 tests pass, typecheck exit 0;@objectstack/platform-objects97 files / 1369 tests pass. At90114641:uipackages/i18n/89 files / 1374 tests pass,@object-ui/i18ntype-check exit 0 (no ui file changed since). Earlier, atbf7f2b74,uipackages/i18n/ plugin-detail/ plugin-gantt/ types/15806 tests pass.Acceptance notes
mainis merged in, not rebased. Two merge commits:94161588(fix(spec)!: a fixed currency config needs its code — currencyConfig.defaultCurrency loses its CNY default #27218) ande527c3b6(feat(spec)!: retire the never-consumed CurrencyValueSchema / CurrencyValue export (ADR-0104 D1) #27233, docs(adr): ADR-0144 multi-currency — record currency, dated rates, write-time home projection; dated note on ADR-0104 #27239, base73725c20), each followed by its own regeneration commit (a9f5abfd,90114641). The second merge conflicted only on the object-lifecycle positive-control count (combined to 695). The os-regen driver kept this branch's side ofauthorable-surface/data.json, which still listed theCurrencyValuekeys feat(spec)!: retire the never-consumed CurrencyValueSchema / CurrencyValue export (ADR-0104 D1) #27233 retired; the baseline was taken frommainand rebuilt, so the only line this PR adds there iscurrencyField.ad89eb22). Code, test, changeset and the two new anchors cite only D2, whose wording matches ("An amount field declarescurrencyMode: 'record'. ItscurrencyFieldnames a text field on the same object withvalueDomain: 'iso_4217_currency'"; the value stays a bare number, ADR-0104 D1 kept). The skills naming convention matches D8 rule 2. D-numbers are unchanged; nothing here cites Open item 12 or the Builds-on ADR-0112 sentence.check-adr-anchorsis green.@objectstack/formula'scurrencyOf(packages/formula/src/formula-return.ts) maps every non-fixedconfig todynamic, soinferFormulaReturnover arecord-mode amount would stamp a currency formula with nocurrencyConfig(tenant currency) instead of{ currencyMode: 'record', currencyField }. Not reachable before this PR lands; carried as a declared rider on objectql: record-mode guards — blank code refuses the write; ungrouped sum/avg/min/max over a record-mode field refused; summary roll-up over one refused at registration (multi-currency M0-2, epic #8345) #27186.ObjectExtensionSchemadoes not judge the pointer: an extension's fields merge into an object it does not hold, so its pointer may name an owner-declared field (stated in the helper's docblock).check:i18n,check:i18n-stale-fillandcheck:i18n-coverageare green with the hand-translated three-mode texts.currencyConfig/currencyMode; documenting the record mode there is left to docs(data-modeling): the currency page — dynamic / fixed / record modes, the sibling code field, what refuses (multi-currency M0-5, epic #8345) #27189.Contract-review follow-ups (review 6487611079 at
90114641, PASS)@objectstack/platform-objectsadded to the changeset.currencyField.min(1); ④.1bdefaultCurrencyrefused underrecord; ④.1c / ④.2b ADR-0144 anchors onfield.zod.tsandobject.zod.ts.VALUE_DOMAIN_FIELD_TYPES.currencyModehelp text now describes three modes (plus thecurrencyConfiganddefaultCurrencytexts that the new rule changed).AMOUNT_home/AMOUNT_ratedropped, record-mode example added, within the ratchet.os-zhuangapproval on5232c8b1(escalated to the maintainer), and the inheritedAddFieldOperationledger drop (fix(spec)!: a fixed currency config needs its code — currencyConfig.defaultCurrency loses its CNY default #27218).Verification
Dispatch gates — 123 derived, 123 run, all exit 0 at b6f152b (reconciled with --ran)
Derived with
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackatb6f152bd(20 paths vs merge base73725c20); every family run with the closure built, each exit code recorded; reconciliation: "123 derived famil(ies) accounted for — 123 run, 0 NOT-MEASURED (a DERIVED zero — all 123 recorded an exit code and none of them is 3)". All 123 exit 0.check:generated(spec): all artifacts current.Earlier rounds, kept for the record: at
90114641, 115 / 115 exit 0; at5232c8b1, 112 derived / 112 run, withcheck-skills-token-ratchet(fixed by deletion in the same file) andcheck:i18n(bundles regenerated and translated) red on the first pass and green after; ata9f5abfd, 112 / 112 exit 0.CI at b6f152b — 65 check runs, 58 success, 7 skipped, 0 failed
The same set as below, all success again: Lint & Repo Gates, TypeScript Type Check, Test Core (1–8/8), Test UI (1–16/16), Type Check UI, Lint UI, Build & E2E UI, Live E2E UI, Dogfood Regression Gate, Build Core / Docs / UI Deps, Check Changeset, PR-body guards, Governed Surface Queue Guard and the rest; the same 7 opt-in or schedule-gated jobs skipped.
CI at 9011464 (the reviewed head) — 65 check runs, 58 success, 7 skipped, 0 failed
Success: Lint & Repo Gates (and its Lint · … jobs), TypeScript Type Check (and its Type Check · … jobs), Test Core (1–8/8), Test Core, Test UI (1–16/16), Test UI, Type Check UI, Lint UI, Build & E2E UI, Live E2E UI, Dogfood Regression Gate, Build Core, Build Docs, Build UI Deps, Plan Test Core shards, Check Changeset, Check PR Size, Check Documentation Links, Spec property liveness, Governed Surface Queue Guard, Flag docs affected by code changes, PR-body guards, Auto Label, filter. Skipped (opt-in or schedule-gated): Dogfood Verify CLI, Dogfood Regression Gate matrix, Temporal Conformance, UI Published Artifacts, 18.x upgrade harness, Packed-tarball smoke, Schedule gate.
Generated by Claude Code
🤖 Generated with Claude Code
https://claude.ai/code/session_01Utp6CbfVSc1hXtxLUPHZ2f