Skip to content

fix(spec): never materialize the currency precision default the schema itself refuses — bare fixed-JPY parse is idempotent (#11423) - #11766

Merged
os-warren merged 2 commits into
mainfrom
claude/issue-11423-currency-precision-idempotent
Aug 24, 2026
Merged

os-warren merged 2 commits into
mainfrom
claude/issue-11423-currency-precision-idempotent

Conversation

@os-warren

@os-warren os-warren commented Aug 24, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #11423

What

CurrencyConfigSchema's .overwrite() no longer materializes the relocated precision default (2) onto the one combination whose authored spelling its own superRefine refuses: currencyMode: 'fixed' with a defaultCurrency whose ISO 4217 / CLDR fraction digits contradict 2 (the JPY/KRW/KWD class). A bare fixed-JPY config now parses to output that OMITS precision, so parse(parse(x)) holds on the mainline ObjectSchema.create() → defineStack re-parse chain. One conditional, mirroring the #11406 master_detail guard one screen below in the same file; the superRefine rejection of an AUTHORED contradictory precision is untouched, message and path included.

Governing ruling

Routed by the spec seat under the maintainer's ruling on #9689 (comment 5393503175), verbatim:

「The same principle prescribes the fix for the #7918 currency twin (#11423) — the spec seat should route it under this ruling.」

The principle, as landed by PR #11406 (commit 9086761ed): never materialize a default the schema itself would refuse as authored.

Reverse verification (both legs, measured)

Unfixed source (branch base 2a6122bd9):

Fixed source (this branch, head c3613e137):

  • parse({ currencyMode: 'fixed', defaultCurrency: 'JPY' }) → {"currencyMode":"fixed","defaultCurrency":"JPY"}; re-parse → success, no issues
  • full pin file + field tests: 2 files / 192 tests passed

Behavior matrix (pinned)

input before after
bare fixed-JPY/KRW/KWD materialized precision: 2; re-parse REJECTED omits precision; re-parse green (idempotent)
authored precision: 2 + fixed JPY rejected rejected — same message, same path
bare fixed-USD (2-digit class) precision: 2 precision: 2, byte-identical
bare dynamic (any code) / fixed unknown code (BTC) precision: 2 precision: 2 — the superRefine cannot refuse these

Contract note

Clause-②: yes — parse output changes for previously-accepted bare fixed-currency inputs; the PR stays draft, the contract-review chain runs before enqueue.

Stored rows / built artifacts that carry the OLD baked precision: 2 on a fixed zero/three-digit currency re-parsed RED before this change (that is the defect) and still read as authored contradictions after it; same population shape #11406 handled with a no-automatic-conversion migration entry on its twin. Whether this card owes its own registry entry is left to the contract review — the dispatch's declared file surface deliberately did not include the migration registry, and no consumer outside packages/spec reads currencyConfig.precision (measured: analytics/dogfood read only defaultCurrency).

Verification — real readings at head c3613e137

Gate derivation line (quoted per dispatch): dispatch-gates: gate list derived from the tree of 'objectstack-ai/objectstack' at commit c3613e137 (/home/user/objectstack-11423). — change set 3 path(s) vs merge base 2a6122bd9 (field.zod.ts, currency-precision-iso4217.test.ts, the changeset).

Heavy runs all through scripts/pm/os-verify-lock.sh; every exit captured before any pipe; each reading quotes the gate's own verdict line:

run reading
pnpm --filter @objectstack/spec test (full) Test Files 420 passed (420) · Tests 11224 passed (11224) · VERDICT command-exit 0
pnpm --filter @objectstack/spec typecheck (tsc + scripts + test-typecheck) VERDICT command-exit 0
pnpm --filter @objectstack/spec check:generated VERDICT command-exit 0 — no stale artifact, tree clean after
examples/app-showcase typecheck && vitest run Test Files 26 passed (26) · Tests 362 passed (362) · VERDICT command-exit 0
pnpm --filter @objectstack/lint test Test Files 81 passed (81) · Tests 2280 passed (2280) · VERDICT command-exit 0
CLI test/migrate-meta.e2e.test.ts Test Files 1 passed (1) · Tests 14 passed (14) · VERDICT command-exit 0
qa/dogfood test/expression-conformance.test.ts Test Files 1 passed (1) · Tests 3 passed (3) · VERDICT command-exit 0
turbo run build --filter=./packages/* --filter=./packages/*/* 70 successful, 70 total · VERDICT command-exit 0

Derived gate families, each exit 0 (captured pre-pipe): nul-bytes · changeset-no-major · adr-0087-registration · empty-changeset · cross-package-test-inputs (both spellings) · test-source-alias · merge-driver · objectui-changeset · spec-parsed-alias · slot-lookup · published-files · type-source-resolution · where-matcher · engine-double-contract · query-options-erasure · changeset-gate-self-tests · dev-prereqs (post-build) · plugin-teardown-shape · docs-audit affected-docs · release-rehearsal-clone --self-test · lint-pkg doc-formula-expressions · spec check:liveness / check:empty-state / check:strictness-ledger / check:variant-docs · check:type-check-coverage.

One run predates the head commit by content-irrelevant bytes: the full spec suite started at f96b1c3ec (the fix commit); the only later commit c3613e137 adds .changeset/thin-yen-keeps-no-cents.md, which no test reads. Every other reading above was taken at c3613e137.

Declared narrowing (CI runs the full farm regardless): repo-wide check:type-check-debt --re-measure was not run locally — the diff touches only @objectstack/spec, which has no entry in the root debt ledger (its test-typecheck debt lives in packages/spec/test-typecheck-debt.json, gated by spec's own check:test-typecheck, executed inside the spec typecheck reading above), and spec's emitted .d.ts surface is unchanged (check:generated green includes check:api-surface), so no other package's ledger count can move from this diff. Repo-wide pnpm lint (eslint sweep) is CI-owned and was not run.


Generated by Claude Code

claude added 2 commits August 24, 2026 15:30
…xed currency whose fraction digits refuse it (#11423)

The CurrencyConfigSchema .overwrite() baked precision 2 onto a bare
fixed-JPY/KRW/KWD-class config; the superRefine refuses that spelling as
authored, and the two are indistinguishable by design - so parse output
rejected itself on the mainline ObjectSchema.create() -> defineStack
re-parse. One conditional in the .overwrite() (the #9689 master_detail
precedent one screen away): the refused combination parses to output that
OMITS precision; every other combination keeps byte-identity. parse(parse(x))
pinned idempotent at CurrencyConfigSchema, FieldSchema and the
create() -> defineStack chain; authored contradictions stay rejected with the
same message; bare fixed-USD still materializes 2 byte-identically.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rxnd8cyFnoU8V5y21PaTsy
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them. ✅

What this run could not see
  • the SDK route bridge reached 45 of 222 client-bound route-ledger rows — the other 177 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run: node scripts/docs-audit/affected-docs.mjs --bridge-coverage

Coarse fallback — 126 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 6d0cccc7271f295cac1d46423285ee0e8c4cb78d → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 005d5f65b7d4bdd958b83b95ee28c51972df64aa — the merge of head c3613e13715d3a71d27000481e24196bd30915ab into base 6d0cccc7271f295cac1d46423285ee0e8c4cb78d, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 005d5f65b7d4bdd958b83b95ee28c51972df64aa && git checkout 005d5f65b7d4bdd958b83b95ee28c51972df64aa
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 6d0cccc7271f295cac1d46423285ee0e8c4cb78d c3613e13715d3a71d27000481e24196bd30915ab && git checkout -B drift-repro 6d0cccc7271f295cac1d46423285ee0e8c4cb78d && git merge --no-ff c3613e13715d3a71d27000481e24196bd30915ab

node scripts/docs-audit/affected-docs.mjs --json 6d0cccc7271f295cac1d46423285ee0e8c4cb78d

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation protocol:data tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface labels Aug 24, 2026
@os-warren os-warren added the needs:contract-review Marker only — PR awaits its at-tier Contract review PASS; no check reads it, the record decides label Aug 24, 2026 — with Claude
@os-zhuang os-zhuang removed the needs:contract-review Marker only — PR awaits its at-tier Contract review PASS; no check reads it, the record decides label Aug 24, 2026 — with Claude
@os-warren
os-warren marked this pull request as ready for review August 24, 2026 21:39
@os-warren
os-warren added this pull request to the merge queue Aug 24, 2026
Merged via the queue into main with commit c4db311 Aug 24, 2026
42 checks passed
@os-warren
os-warren deleted the claude/issue-11423-currency-precision-idempotent branch August 24, 2026 21:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation protocol:data size/m tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Projects

None yet

3 participants