Skip to content

[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

Closed

Description

@objectstack-fleet

Filing gate: ① a defect, class (c): an author, AI or human, sets currencyConfig.precision expecting it to decide a currency's decimal places, and nothing honours it. Filed by domain:spec execution seat 1 (session_013RDBh5DqXd2xnLwvHLgLFr) because the maintainer's ruling orders it. ⛔ Not graded or routed here; that is triage's.

Provenance. Ruling 乙 on #19910 (record 5805782503, batch #218 item 2, maintainer 「其他同意」), item 3, verbatim: 「Two findings follow through the filing gate, filed by the domain:spec seat, ⛔ not riders: currencyConfig.precision is declared and read by nothing (ADR-0049 enforce-or-remove); objectui's CurrencyField reads the field-level precision (a digit count) as decimal places.」 This is the first of the two. The second goes to objectstack-ai/objectui.

Readings, re-taken at filing on origin/main c8399867b8

  • Declared: packages/spec/src/data/field.zod.ts:1663 declares currencyConfig: CurrencyConfigSchema.optional(), and CurrencyConfigSchema carries precision.
  • Its only reader is its own validation: field.zod.ts:457-458 checks an authored precision against the currency's ISO 4217 digits, via currencyPrecisionContradiction.
  • Nothing reads it for behaviour. A git grep -n -E "currencyConfig" over packages/*/src, non-test, finds these, and none of them reads precision:
    • driver-sql/src/builtin-column-collision.ts:89 (storage mode);
    • service-analytics/src/analytics-service.ts:1794 and plugin.ts:1070-1084 (defaultCurrency only);
    • spec prose.
  • Firing control: the same instrument finds currencyConfig?.defaultCurrency read at service-analytics/src/plugin.ts:1084.
  • Renderer side, at .objectui-sha 62597c588072: packages/fields/src/widgets/CurrencyField.tsx resolves decimals from the field-level precision or the ISO 4217 minor unit. The file's own comment says the currencyConfig key is one 「this widget never reads」 ([Decision] Ruling 5791803339 points currency authors at currencyConfig.precision — but nothing reads that key, and removing scale leaves two console faces rendering currency with zero decimals until objectui#10221 lands #19910 triage 5800441682).

⇒ A declared, validated key whose value changes nothing a user sees.

The decision this card carries (ADR-0049)

Enforce it (a named consumer reads currencyConfig.precision), or remove it (a retirement with its ADR-0087 entry). Ruling 乙 on #19910 already set the direction for currency: 「a currency's decimal places are the currency's, not a setting」. That reads toward removal, but ⛔ this card does not decide it.

Related, ⛔ not folded

Dedupe

mcp__github__search_issues, repo-scoped: 「currencyConfig.precision declared but nothing reads it enforce-or-remove」 → 2 hits, #19910 (closed, the ruling's own card) and #7918 (closed, a publish-time contradiction question). No open card carries this.

Activity

  1. objectstack-fleet commented on Sep 24, 2026

    @objectstack-fleet
    ContributorAuthor

    Blocked-by: #19909

    分诊首次定级:priority:p3 · bug · domain:spec · pm:blocked —— currencyConfig.precision 声明了、也校验了,但没有任何渲染或运行时读取它

    Path: packages/spec/src/data/field.zod.ts(currencyConfig 声明 :1663,校验 :458)

    Triage: lands in the spec field contract ⇒ domain:spec, bug, priority:p3, pm:blocked Blocked-by #19909 (the in-flight currency scale retirement, which edits the same field.zod.ts); rationale: an ADR-0049 declared-but-unenforced key filed on ruling 乙 of #19910 — an author who sets it expects decimal places and gets none — and ruling 乙's own principle 「a currency's decimal places are the currency's, not a setting」 points the enforce-or-remove choice at removal.

    分诊席(session_01Tw7jnJinGHvoGSi8aFkhPJ,座位贴 #6015),2026-09-24T15:30Z。⛔ 不认领、不派发。本席读完了卡面(本卡尚无评论),并在 main(a7581b326c)上核对了代码,读了 PR #19909 的改动文件。

    本席核对

    定级说明

    执行要点

    • 按 spec 属性退役的规程做:收窄已发布的写法 ⇒ Clause-② 和 ADR-0087 登记;生成物用生成命令更新,⛔ 不手改。
    • objectui 侧的配套问题(CurrencyField 把字段级 precision 当小数位读)按裁定另开在 objectui,⛔ 不并进本卡。

    Generated by Claude Code

  2. objectstack-fleet commented on Sep 24, 2026

    @objectstack-fleet
    ContributorAuthor

    Evidence from the domain:spec execution seat 1 (session_013RDBh5DqXd2xnLwvHLgLFr), 2026-09-24T18:31Z. These are two neighbours measured by the round-3 dev of PR #19909 on its head d7740c324e. ⛔ This is not a claim, ⛔ no label is changed, and ⛔ no new card is filed. The card's disposition carries them.

    1. Aliases into the unread key. packages/spec/src/data/field.zod.ts:420: CurrencyConfigSchema renames scale → precision and decimals → precision. An author who writes currencyConfig.scale has it moved into currencyConfig.precision, the key this card records as read by nothing. Whatever enforce-or-remove disposition this card takes has to take the aliases with it.
    2. Help text that contradicts the field key's describe.
  3. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    ContributorAuthor

    Unlock scan: the same-file holder merged · pm:blocked → pm:queue

    Triage seat (objectstack-wide, seat post #6015) · session_01W89enF2dYV7K4N2Fbfj33f · 2026-09-27T06:01Z. ⛔ 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:p3 · domain:spec stay.

  4. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    ContributorAuthor

    Family site appended: the FIELD-level precision also has no reader · 2026-09-27T09:22Z

    domain:spec seat 1 (session_01Rjy9MeetSfq34PKn81CRiN), from the #20011 dev report (PR #20209, out_of_scope_findings[0]). ⛔ Not a claim. It is recorded here because this card owns the family 「a declared precision key that nothing reads」, and the closure rule appends sites to the family card rather than filing them singly.

    Dedupe words: field precision no reader · Total digits precision unenforced · liveness field.json precision stale evidence

  5. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 · 2026-09-27T10:51Z
    Session: session_01Rjy9MeetSfq34PKn81CRiN
    Account: os-zhuang
    Branch: claude/issue-19992-currency-config-precision-retire
    Worktree: objectstack-issue-19992
    Domain: domain:spec
    Seat: domain:spec#1
    File surface: packages/spec/src/data/field.zod.ts CurrencyConfigSchema only: the precision key (:443 on 3875ae67), its aliases decimals / scale (:428; the other aliases stay), its ISO 4217 check (.superRefine / .overwrite); packages/spec/src/data/currency-fraction-digits.ts if the helper loses every reader; packages/spec/src/data/field.form.ts:78 (the field-level precision help text only); one ADR-0087 entry + packages/spec/src/migrations/registry.ts; packages/spec/liveness/field.json; the data-modeling doc sentences that state this rule; generated artefacts; tests; .changeset/. ⛔ Not the defaultCurrency describe (open PR #20217). ⛔ Not FieldSchema.precision itself. (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: default judgment tier (the default slot is taken; a retirement with a premise to measure first). The diff hits the enqueue gate's path limb, so it is reviewed at CONTRACT_REVIEW_TIER before enqueue.
    Clause-②: no
    Thread-read: 5854612946
    Serial constraints cleared: #20011 (the same-file sibling triage named) has landed as 84156c7d. Open PR #20217 (#20126, this seat) edits the defaultCurrency describe a few lines below; the dev does not touch it and merges main after it lands. No other open PR touches field.zod.ts, field.form.ts or currency-fraction-digits.ts. The folded field-level precision site (5854612946) is ⛔ not in scope: this PR is Part of #19992 and names it as the remainder. Direction: triage 5853208433 (remove, with the aliases; stop if a real reader is measured).

  6. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 19992,
    "status": "done",
    "branch": "claude/issue-19992-currency-config-precision-retire",
    "pr": "#20251",
    "session": "session_01Rjy9MeetSfq34PKn81CRiN — the PM's (mode:subagent); identity = the branch",
    "premise_still_valid": true,
    "summary": "Retired currencyConfig.precision (ADR-0049 enforce-or-remove, triage direction REMOVE under ruling 乙): strict deletion from CurrencyConfigSchema plus a guidance entry carrying the prescription; the decimals/scale aliases became guidance entries with the same reason; the #7918 .superRefine and the #11423 .overwrite (which baked precision: 2 into parse output) are removed; currencyPrecisionContradiction and currencyFractionDigits (no reader left) removed, CURRENCY_FRACTION_DIGITS kept for the iso_4217_currency value domain. ADR-0087: D2 conversion currency-config-precision-removed (toMajor 18, retiredFromLoadPath, strips the key at rest on objects/objectExtensions), step-18 wiring, RETIRED_KEYS_BY_MAJOR[18] data/CurrencyConfig:precision. Field-form precision help text now 'Total digits' (+ en/zh-CN/ja-JP/es-ES bundles); docs, published skill, 3 showcase objects, generated references and ledgers updated; changeset spec minor + platform-objects patch, BREAKING, adr-0087 registered. PR body: first line 'Part of #19992'; #19992 remains open for the folded FIELD-level precision family site (5854612946). PR is Tier H because skills/** is on the diff.",
    "tests": "All on head 3681939 (batch at 74cf413 + the en bundle commit, tree identical). spec build exit 0; spec check:generated exit 0 (15/15); spec typecheck exit 0 (incl. check:test-typecheck); spec vitest --project local: 546 files passed, 16062 tests passed, 2 todo. Consumers: example-showcase typecheck exit 0; platform-objects typecheck exit 0, tests 55 files / 911 passed; service-analytics currency-mode-relay + query-dataset 51 passed; pnpm check:i18n exit 0 (9 packages in sync). Reverse verification Leg A (ablation-replace, anchor 1→0, blob 58a9af7e→700705a8): showcase tsc exit 1, account.object.ts(71,25) TS2353 'precision' does not exist in type {currencyMode?, defaultCurrency?} against the rebuilt spec .d.ts; restored blob==HEAD, git diff HEAD empty. Ablation Leg B (precision re-declared on the schema, blob 7d36200c→86fdbf2d): 9 failed / 279 passed in the two pin files (every retirement pin red; alias, parse-output and conversion pins stay green by design); restored blob==HEAD, git diff HEAD empty; tree clean after both legs (0 bytes). Direction observed: red. No permanent ablation files left.",
    "gates": {
    "head": "36819398",
    "derived": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack → 120 commands (repo objectstack-ai/objectstack at 3681939; 3 commits behind origin/main, none touching derivation inputs)",
    "reconciliation": "dispatch-gates --ran: 120 derived famil(ies) accounted for — 119 run, 1 NOT-MEASURED (1 DERIVED from a recorded exit 3), 0 UNRUN; exit 0",
    "nonzero_first_pass": [
    "pnpm --filter @objectstack/spec run check:skill-examples :: exit 3 (client-react dist missing) → after pnpm --filter '@objectstack/client-react...' build: exit 0, '259 prose examples type-check across 3 surface(s)'",
    "pnpm check:dual-build-cjs-loads :: exit 3"
    ],
    "not_measured": [
    "pnpm check:dual-build-cjs-loads — reason: prerequisite is every package's dist/ (full pnpm build); this diff changes no package entry or exports"
    ],
    "early": [
    "node scripts/check-adr-0087-registration.mjs --base origin/main exit 0 (1 declared-breaking changeset, registered currency-config-precision-removed)",
    "node scripts/check-changeset-no-major.mjs --base origin/main exit 0",
    "pnpm check:nul-bytes exit 0",
    "node scripts/check-spec-docblock-symbol-anchors.mjs exit 0"
    ],
    "ci": "0 check runs registered on 3681939 at report time — not waited on (PM owns CI convergence)",
    "not_run_locally": "repo-wide pnpm lint (CI's run); cli integration tier (diff touches no packages/cli file); qa/dogfood (boots showcase; CI)"
    },
    "line_budget": {
    "pr_changed_lines": "30 files, +659 / -511 = 1170 vs human-merge threshold 5000: under",
    "skills": "skills/objectstack-data/rules/field-types.md 328 → 327 lines; skills/objectstack-data markdown total 3717 → 3716; all skills/*/SKILL.md 4402 → 4402 (pure removal; an added 3-line note was dropped to keep net ≤ 0)"
    },
    "deviations": [
    "liveness/field.json NOT edited (order listed its currencyConfig row): measured — the row covers the container, asserts nothing about precision and stays live (defaultCurrency read); field/currencyConfig is in scripts/liveness/undrilled-containers.baseline.json so no per-key row existed; #20217 had just re-cited that row.",
    "File surface beyond the claim, each because this change would otherwise leave it false: shared/value-domain.zod.ts docblock (named the removed function), one comment in conversions/registry.ts, 3 examples/app-showcase objects, content/docs fields.mdx + getting-started/common-patterns.mdx + protocol/objectql/types.mdx, skills/objectstack-data/rules/field-types.md (Tier H), platform-objects translation bundles, and generated/ledger rows (authorable-surface/data.json hand-removed per gate (a); authorable-defaults/data.json regenerated; dropped-refinements.baseline.json 13 sites hand-removed; references regenerated). Also comment-only edits elsewhere in field.zod.ts that named the removed twin.",
    "PR is Tier H (skills/** on the diff): landing needs the maintainer's APPROVED review, not only the contract-tier record. The body carries the 维护者速读(草稿) section with 席位意见 left blank.",
    "No tree-scoped absence text pin, deliberately: precision stays authorable as the FIELD-level key, so the retired thing is a key in a position; followed the dashboard-chart-structure-refusal.test.ts precedent (rationale block in the pin file); tsc (Leg A) and the closed parse door (Leg B) stand in.",
    "decimals/scale: guidance entries (strict route, FieldSchema 'currency' guidance precedent) rather than bare unknown-key reports; not converted (never legal, no stored row carries them).",
    "Clause-② copied verbatim from the claim as 'Clause-②: no' (no arm); the change is a narrowing — the changeset's BREAKING banner and adr-0087 marker carry that (check-adr-0087-registration green). The '(narrowing)' arm was not added because the claim did not carry it.",
    "Merged origin/main (3cb84d0, #20217 landed) with scripts/pm/os-regen-merge.sh before opening the PR; 4 conflicts resolved by stacking both sides; sibling entries measured present (same counts as origin/main). The regeneration commit first carried the merge message and was reworded locally before any push.",
    "docs/qa/platform-checklist/areas/records-forms.json not edited (item broadly stale on currency scale from an earlier card) — Acceptance notes.",
    "Verify-lock queue: ~2h of cumulative queue time across batches (99s retried with the same slot; queue progressed throughout)."
    ],
    "files_changed": [
    ".changeset/19992-currency-config-precision-retired.md",
    "content/docs/data-modeling/field-types.mdx",
    "content/docs/data-modeling/fields.mdx",
    "content/docs/data-modeling/validation-rules.mdx",
    "content/docs/getting-started/common-patterns.mdx",
    "content/docs/protocol/objectql/types.mdx",
    "content/docs/references/data/field.mdx",
    "content/docs/references/data/object.mdx",
    "content/docs/references/shared/value-domain.mdx",
    "content/docs/references/system/migration.mdx",
    "examples/app-showcase/src/data/objects/account.object.ts",
    "examples/app-showcase/src/data/objects/field-zoo.object.ts",
    "examples/app-showcase/src/data/objects/semantic-zoo.object.ts",
    "packages/platform-objects/src/apps/translations/en.metadata-forms.generated.ts",
    "packages/platform-objects/src/apps/translations/es-ES.metadata-forms.generated.ts",
    "packages/platform-objects/src/apps/translations/ja-JP.metadata-forms.generated.ts",
    "packages/platform-objects/src/apps/translations/zh-CN.metadata-forms.generated.ts",
    "packages/spec/authorable-defaults/data.json",
    "packages/spec/authorable-surface/data.json",
    "packages/spec/dropped-refinements.baseline.json",
    "packages/spec/src/conversions/registry.ts",
    "packages/spec/src/data/currency-fraction-digits.ts",
    "packages/spec/src/data/currency-precision-iso4217.test.ts",
    "packages/spec/src/data/field.form.ts",
    "packages/spec/src/data/field.test.ts",
    "packages/spec/src/data/field.zod.ts",
    "packages/spec/src/migrations/entries/retired-keys/18.data__CurrencyConfig__precision.ts",
    "packages/spec/src/migrations/registry.ts",
    "packages/spec/src/shared/value-domain.zod.ts",
    "skills/objectstack-data/rules/field-types.md"
    ],
    "mcp_calls": "0",
    "api_writes": "3 relay writes as objectstack-fleet[bot] via repository_dispatch (POST /repos/objectstack-ai/objectstack/dispatches each): (1) pr_create → POST /repos/objectstack-ai/objectstack/pulls (run 36327385351, PR #20251 draft); (2) label-write assign → POST /repos//issues/20251/assignees os-zhuang (run 36327433373, read back); (3) post-stamped → POST /repos//issues/19992/comments (this os-dev-report). Plus 7 git pushes on the paced route (write-pace --kind 'git push'), not REST.",
    "open_questions": [
    {
    "question": "Landing path for the Tier H surface: keep skills/objectstack-data/rules/field-types.md in this PR, or split it into its own PR so the code half lands on the contract-tier record alone?",
    "options": [
    "A — keep one PR (Tier H: maintainer APPROVED review, then the owning seat lands it)",
    "B — split: code/docs PR lands at contract tier; a one-file skills PR waits for the maintainer"
    ],
    "recommendation": "A. 实际业务需求: the skill is what AI authors read, and after the code lands it would teach a refused key — the trap this card removes. 长远合理性: one landing keeps the contract and its teaching text consistent at every commit of main. 防 AI 犯错: under B there is a window where the published skill contradicts the schema. 不扩散: B costs a second PR and review for a 2-line removal. The cost of A is waiting for the maintainer's word."
    }
    ],
    "out_of_scope_findings": [
    "carrier: next checklist-author sweep; 承接者:无 · docs/qa/platform-checklist/areas/records-forms.json item records-forms.field-type-constraints describes f_currency as 'scale 2 currencyConfig{precision 2}' and probes currency scale — stale on both halves; internal QA prose, not published · noted, not filed",
    "carrier: 承接者:无 · standalone 'field' metadata rows are outside every field conversion (applyConversionsToStoredItem has no 'fields' stack collection), this one included, same as its precedents; no producer of such rows measured · noted, not filed"
    ]
    }


    Generated by Claude Code

  7. 11 remaining items

  8. objectstack-fleet commented on Sep 28, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1
    Session: session_01B3TqpoQbTAfG7G74GMDWNW
    Account: hotlong (the seat's linked user as GET /user answers it; the card's assignee; seated on the maintainer's ruling 「放行,以 hotlong 坐席」, seat post #6017 marker 5864561291)
    Branch: claude/issue-19992-field-precision-write-seam
    Worktree: objectstack-issue-19992
    Domain: domain:spec (a vertical Seam: dispatch per triage 5864304093; both ends declared below)
    Seat: domain:spec#1 (seat post #6017)
    File surface (the remainder only: FieldSchema.precision, "Total digits", ENFORCE at the write seam):

  9. objectstack-fleet commented on Sep 28, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 19992,
    "status": "done",
    "branch": "claude/issue-19992-field-precision-write-seam",
    "pr": "#20423",
    "head": "bb33240d",
    "session": "session_01B3TqpoQbTAfG7G74GMDWNW — the PM's (mode:subagent); identity = the branch named by claim 5865023687",
    "premise_still_valid": true,
    "summary": "FieldSchema.precision ('Total digits') is now enforced at the objectql write seam. record-validator.ts refuses a number/currency/percent/rating/slider write whose digit count exceeds the declared count, with 400 VALIDATION_FAILED and field code max_precision, and never rounds. The count is the SQL DECIMAL(p, s) reading: digits are counted at the decimal places the max_scale rule applies (scaleAllowance), or at the value's own when it applies none (undeclared scale, and currency, where scale is refused); a fraction-stored percent is counted two places right (scale+2, or 2 with no scale). Spec end: FieldErrorCode gains max_precision (the ADR-0114 D2 closed catalog, not ERROR_CODE_LEDGER/StandardErrorCode), with four-locale templates max_precision/max_precision_scaled; the precision describe states the rule; the liveness row is re-evidenced at the write seam. No storage or DDL change. Draft PR #20423, Fixes #19992, Clause-②: yes (copied from the claim), assigned hotlong.",
    "premises": {
    "A1": "HELD. git grep -n precision over record-validator.ts at df3ba16: 0 hits (scale: 29). Non-spec packages//src readers: only the fixed NUMERIC_COLUMN_REPRESENTATION numeric.precision (cli generate, driver-sql DDL) and datetime precision.",
    "A2": "DECIMAL(p, s) reading implemented and pinned: p5 s2 refuses 1234.5 (1234.50 = 6) and accepts 123.45/999.99. Undeclared scale: own decimal places, leading zeros never count, integer trailing zeros do (p4: 0.001 ok, 10000 refused). Currency: own decimals count toward the total, decimals themselves unconstrained. Fraction percent: counted at scale+2, or 2 with no scale, i.e. the percentage-point digits as displayed (p4 s2: 0.9999 ok, 1 refused; p3 no scale: 10 = 1000% refused). p below s: |v| below 10^(p-s) (p1 s2: 0.05 ok, 0.1 refused).",
    "A3": "The vocabulary is FieldErrorCode in packages/spec/src/api/errors.zod.ts (ADR-0114 D2 closed field-level catalog); NOT a new ERROR_CODE_LEDGER / StandardErrorCode member (the top-level code is the already-registered VALIDATION_FAILED). Registration also needed four-locale templates (validation-message.ts), a foreign-vocabulary row in packages/runtime/src/dispatcher-error-vocabulary.ts (check:dispatcher-error-vocabulary reds without it) and the error-catalog.mdx row.",
    "A4": "Census at df3ba16, regex \bprecision\s
    :\s*[0-9]+ minus currencyConfig/datetime/generated: 0 field-level precision in examples, templates, platform-objects, seeds or JSON fixtures. Positive control: the same form for scale hits examples/app-todo (2). Two test fixtures (precision 5 scale 0, values 1-12) all fit. No seed value is refused.",
    "A5": "Matches. objectui pin f8a9d0fb ObjectFieldInspector.tsx:914-921 writes top-level precision labelled 'Precision' / 精度 beside 'Scale' / 小数位; its offersScale docblock calls it the TOTAL digit count. No finding.",
    "A6": "validateRecord call sites (engine.ts): validate() dry run, insert() one row and an array (whole batch refused), update() by id and by predicate (multi). insertMany routes through insert with per-row outcomes. REST create/createMany/batch/import reach these; the import create leg is createManyData → engine.insert(rows[]). update.options.upsert is a retired tombstone, so there is no separate upsert door. Pinned: insert one, insert[], insertMany, update by id, update multi, validate, REST create (400 envelope), REST import (dry run + commit)."
    },
    "tests": "Heavy runs through os-verify-lock (slot issue-19992-precision). AT bb33240 (final head): objectql validator suites (precision, record-validator, number-value) 3 files 247/247 plus rest import-integration.test.ts 44/44 (42 on main + 2 new), one VERDICT command-exit 0; objectql typecheck + rest typecheck + rest zod-union-fields.test.ts 18/18, VERDICT 0; spec build (after the merge) exit 0; spec check:generated 'All 15 generated artifacts are up to date'; spec typecheck exit 0; spec --project local over src/api src/system src/data scripts/liveness 202 files, 6840 passed, 1 todo (a declared narrowing; the rest of spec is declared to CI). Earlier heads: objectql full --project local 326 files / 6058 passed (d6056d9; the merge brought no objectql change); spec touched suites (validation-message, errors, field, currency-precision-iso4217) 356/356 (563e11d). ABLATION at bb33240, with the fix committed first: scripts/ablation-replace.mjs anchor = the if that compares actual with def.precision, replaced by if (false) with an ABLATION-19992 marker, anchor 1→0, blob 84ef8a2e→c928a426; the pin file went 18 failed / 4 passed (the 4 green are the controls: undeclared precision, ordering, malformed declaration, fitting-value doors). Restored: blob 84ef8a2e == HEAD, git diff HEAD empty, marker count 0. Direction observed: red. The subject resolves via a relative import to src, so no dist rebuild was involved.",
    "gates": {
    "head": "bb33240d",
    "derived": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at bb33240: 114 commands (the dispatch list's 77 + 37 added by the diff); identical list at d6056d9",
    "reconciliation": "dispatch-gates --ran: 114 derived famil(ies) accounted for — 111 run, 3 NOT-MEASURED (3 DERIVED from a recorded exit 3), 0 UNRUN; exit 0",
    "nonzero_first_pass": [
    "pnpm check:error-code-casing :: exit 1 at 563e11d's parent (four code: assertions in the new pin file not field-addressed) → field: added → exit 0",
    "pnpm check:dispatcher-error-vocabulary :: exit 1 (max_precision stamp site unclassified) → foreign-vocabulary row added → exit 0",
    "node scripts/check-engine-split-ratio.mjs --days 90 :: exit 2 (shallow clone refusal) → git fetch --shallow-since=2026-06-23 origin main, as it prescribes → exit 0",
    "pnpm --filter @objectstack/spec check:generated :: exit 1 at d6056d9's parent (check:docs stale) → gen:docs → 15/15"
    ],
    "not_measured": [
    "pnpm check:dual-build-cjs-loads — reason: exit 3, needs every package's dist (whole-repo build); this diff changes no package entry or exports",
    "pnpm check:type-check-debt — reason: exit 3, needs the full packages build (lint.yml builds it first)",
    "pnpm --filter @objectstack/spec run check:skill-examples — reason: exit 3, needs the client-react closure built; this diff touches no skill and no client"
    ],
    "ci": "32 check runs registered on bb33240 at report time: 12 success, 2 skipped, 18 in_progress, 0 failing. Not waited on (PM owns convergence).",
    "not_run_locally": "repo-wide pnpm lint (CI's); runtime typecheck/tests (closure unbuilt here — rest, plugin-auth, service-cluster/job/messaging dist missing; the one runtime edit is a declaration-table row read by the green check:dispatcher-error-vocabulary and matching the conformance test's rules by shape); the rest of the spec/rest/objectql suites beyond the files named in tests"
    },
    "line_budget": {
    "pr_changed_lines": "16 files, +638 / -16 = 654 vs human-merge threshold 5000: under",
    "skills": "no skills/** file touched; no governed surface on the diff (not Tier S/H)"
    },
    "deviations": [
    "File surface beyond the claim, each because the order or a gate required it: packages/rest/src/import-integration.test.ts (the order's REST-envelope pin); packages/runtime/src/dispatcher-error-vocabulary.ts (the gate-required classification of the new stamp site — not published: imported only by a runtime test, runtime's tsup entry is src/index.ts, so no runtime changeset); packages/spec/src/system/validation-message.ts + its test (the code's templates, part of registration); content/docs/api/error-catalog.mdx (the catalog's bounded-ranges row); content/docs/protocol/objectql/types.mdx (the currency line taught 'precision (0-10, default 2) for decimal places', which enforcement turns into refused writes); generated references (gen:docs).",
    "Engine-door pins live in packages/objectql/src/validation/record-validator.precision.test.ts (inside the claimed validation/ directory) rather than a new engine-*.test.ts beside engine.ts.",
    "The fraction-stored percent is counted at a two-place floor even with no scale declared (the scale+2 shift applied at scale 0). Reason: without it a percent's precision would count stored-fraction digits when scale is undeclared and displayed digits when it is declared. Pinned; stated in the describe and the PR body.",
    "Clause-② copied verbatim as 'Clause-②: yes' (no arm), in the PR body and the changeset. The fuller spelling for this diff is 'yes (narrowing)'. The BREAKING banner and the adr-0087 marker carry the narrowing (check-adr-0087-registration green: not-required (no-migration-prescription)).",
    "Merged origin/main once with scripts/pm/os-regen-merge.sh (merge 1c82935): the os-regen driver kept one side of references/data/object.mdx, and pre-commit refused the hand-off until it was regenerated. gen:schema + gen:docs ran on the COMMITTED merge (MERGE_HEAD absent, so not in MERGE state), unlocked as generators, then bb33240. Sibling counts origin/main/HEAD: action-aria-removed 20/20, filter-cross-field-comparison-class 2/2.",
    "Two commands went past the 600 s foreground ceiling while still running (a gate batch; a spec run that acquired the lock late). Each was waited on in the foreground with tail --pid until it exited, and its log was read. Nothing was left running.",
    "Verify-lock: five queue-timeouts (exit 99, NOT MEASURED, slot kept), then re-acquired. The holders were other sessions' spec test:repo / build runs; the queue moved throughout."
    ],
    "files_changed": [
    ".changeset/19992-field-precision-write-seam.md",
    "content/docs/api/error-catalog.mdx",
    "content/docs/protocol/objectql/types.mdx",
    "content/docs/references/api/errors.mdx",
    "content/docs/references/data/field.mdx",
    "content/docs/references/data/object.mdx",
    "content/docs/references/system/migration.mdx",
    "packages/objectql/src/validation/record-validator.precision.test.ts",
    "packages/objectql/src/validation/record-validator.ts",
    "packages/rest/src/import-integration.test.ts",
    "packages/runtime/src/dispatcher-error-vocabulary.ts",
    "packages/spec/liveness/field.json",
    "packages/spec/src/api/errors.zod.ts",
    "packages/spec/src/data/field.zod.ts",
    "packages/spec/src/system/validation-message.test.ts",
    "packages/spec/src/system/validation-message.ts"
    ],
    "mcp_calls": "0",
    "api_writes": "3 relay writes as objectstack-fleet[bot], each a repository_dispatch (POST /repos/objectstack-ai/objectstack/dispatches): (1) pr_create → POST /repos/objectstack-ai/objectstack/pulls (run 36409109961, PR #20423 draft; body read back byte-identical); (2) label-write --assign hotlong → POST /repos//issues/20423/assignees (run 36409198983, read back: assignees hotlong); (3) post-stamped os-dev-report → POST /repos//issues/19992/comments (this comment). Plus 6 git pushes on the paced route (write-pace --kind 'git push': the empty-branch probe, dc6e8a6, 897593a, 563e11d, d6056d9, bb33240), not REST. Zero label adds: the order named none, and skip-changeset does not apply (a changeset ships).",
    "open_questions": [
    {
    "question": "Currency's applied scale: this seam resolves no currency, so a currency amount is counted at its own decimals (a 17-digit integer USD amount fits precision 18). The spec's DECIMAL(18,2) reading of a fixed-USD field would bound the integer part at 16. Keep, or derive the minor unit?",
    "options": [
    "A: keep. Count at the amount's own decimals, one rule for fixed and dynamic currency, no currency resolution at the validator.",
    "B: for currencyMode fixed, count at max(ISO 4217 minor unit, own decimals); dynamic stays as A. This needs CURRENCY_FRACTION_DIGITS at the validator."
    ],
    "recommendation": "A. 实际业务需求: the gap lies above 2^53 (about 16 significant digits), where a double holds no exact amount, and no in-repo field declares precision on a currency. 长远合理性: B makes one key count two ways depending on currencyMode. 防 AI 犯错: A is one rule an author can predict from the describe. 不扩散: B adds currency resolution to the write seam, a capability this card was not given."
    }
    ],
    "out_of_scope_findings": [
    "carrier: 承接者:无 · boundary, not class (c): content/blog/protocol-first-development.mdx:652,680-681 and metadata-driven-architecture.mdx:263 teach precision: 1/2 as decimal places; enforcement makes following them refuse ordinary values (budget of 100 or more, hours of 10 or more). The posts use shapes already refused at parse (ObjectProtocol.define, default:, enable), so there is no live producer · noted, not filed · dedupe words: blog precision decimal places, stale blog field examples",
    "carrier: 承接者:无 · observation: FieldSchema accepts precision on every field type and it is read only on the five numeric types. No producer writes it elsewhere (object.form number/currency/percent, field.form number/currency, designer isNumeric) · noted in Acceptance notes, not filed · dedupe words: precision non-numeric inert, precision applicability refinement",
    "carrier: 承接者:无 · observation: precision: 0 parses (min 0) and refuses every non-zero write. Loud, not silent; a producer-side min(1) would be a separate narrowing · noted, not filed",
    "carrier: next checklist-author sweep; 承接者:无 · docs/qa/platform-checklist/areas/records-forms.json item records-forms.field-type-constraints still says precision/scale are 'DECLARED but NOT enforced on the write path' (already stale for scale); internal QA prose, not published · noted, not filed"
    ]
    }


    Generated by Claude Code

  10. objectstack-fleet commented on Sep 28, 2026

    @objectstack-fleet
    ContributorAuthor

    Review: ACCEPT · PR #20423 at head bb33240df524 · 2026-09-28T10:33Z

    domain:spec seat 1 (session_01B3TqpoQbTAfG7G74GMDWNW), reviewer of record under claim 5865023687. Dev report on this card (os-dev-report). Readings below are the seat's, taken on GitHub or on origin/main.

    check reading
    Shape Draft, base main, first body line Fixes #19992, which is the only closing keyword. mergeable_state blocked (draft), not dirty.
    Scope 16 files, +638 / −16. The core is inside the claim: record-validator.ts plus its new pin file, the FieldSchema.precision describe, and the liveness/field.json row. The extras each follow from the order or a gate: the REST import pin (the order asked for an envelope pin); the FieldErrorCode member in errors.zod.ts and its four-locale templates in validation-message.ts (A3's registration); the dispatcher-error-vocabulary.ts row (check:dispatcher-error-vocabulary requires it); error-catalog.mdx, and the types.mdx currency line, which taught precision as decimal places and is now false; and the generated references.
    Changeset @objectstack/objectql minor + @objectstack/spec minor, with a BREAKING banner and Clause-②: yes. There is no runtime changeset, which is correct: packages/runtime/src/dispatcher-error-vocabulary.ts is not reachable from runtime's tsup entry (src/index.ts; http-dispatcher.ts names it only in a comment), so it does not ship.
    Governed / size check-governed-merges.mjs --pr 20423: not governed; 654 changed lines.
    Code A3: the new code joins FieldErrorCode (ADR-0114 D2's field-level catalog), not ERROR_CODE_LEDGER / StandardErrorCode; the top-level code stays VALIDATION_FAILED.
    Evidence The report's ablation, taken at bb33240d with the fix committed first, turned the pin file 18 red / 4 green, and the 4 green are the controls.
    CI Running at the time of this record; judged at the landing window, not here.

    Flags carried to the at-tier review (③):

    1. The fraction-stored percent is counted at a two-place floor even with no scale. This is the dev's own reading, pinned and stated in the describe.
    2. open_questions[0] (currency counted at its own decimals). The dev recommends A (no currency resolution at the validator). The seat agrees: one rule an author can predict. It is the reviewer's to confirm or escalate.
    3. types.mdx is a published-doc correction that rides this PR because enforcement made the old line false.

    Out of scope, all Acceptance notes (no reach:, nothing filed): the two blog posts that teach precision as decimal places (their shapes are already refused at parse); precision accepted on non-numeric types and inert there; precision: 0 refusing every non-zero write; and the stale QA checklist prose on records-forms.field-type-constraints.

    Next: needs:contract-review goes on PR #20423 in this act. The isolated at-tier review runs when a slot frees; on PASS and a green head, the seat lands it through the queue.

  11. objectstack-fleet commented on Sep 28, 2026

    @objectstack-fleet
    ContributorAuthor

    Close-out by the domain:spec seat 1 (session_01B3TqpoQbTAfG7G74GMDWNW) at 2026-09-28T15:11Z.

    Delivered. PR #20423 merged as b98fbc21a through the merge queue, and this card closed completed on its Fixes line. A field's declared precision (total digits, the p of DECIMAL(p, s)) is now enforced at the objectql write seam: an oversize write is refused 400 VALIDATION_FAILED with the field code max_precision. There is no storage or DDL change. At-tier record 5868385995 PASS at bb33240d. pm:dispatched removed. On main after the merge, the validator arm and max_precision are present, and the regenerated field reference carries the total-digit describe.

    What remains, and who carries it.


    Generated by Claude Code

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

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions