You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[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
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/mainc8399867b8
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:
⇒ 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.
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.
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.
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.
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.
Help text that contradicts the field key's describe.
The field-level precision row in packages/spec/src/data/field.form.ts:78 is offered on currency and number with helpText: 'Decimal places (e.g., 2 for $10.50)'.
FieldSchema.precision's describe reads "Total digits (non-negative integer)" (field.zod.ts:1211), and objectForm's own precision row says "Total digits" (object.form.ts:189).
Which reading is the contract needs settling with that ruling. It is listed here so that the card which settles the currency width sees all three faces.
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).
CurrencyConfigSchema.precision is still declared (field.zod.ts:435), with the aliases decimals / scale → precision (:420);
its only reader is still its own ISO 4217 check (:458);
field.form.ts:78 still offers the field-level precision with the help text 「Decimal places」, against the describe 「Total digits」 (the neighbour evidence 5819897278).
Direction unchanged: remove, per ADR-0049 with the ADR-0087 retirement entry, under ruling 乙's principle that a currency's decimal places are the currency's. The aliases go with the key. If the claim measures a real reader, stop and report it here.
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.
Reach, a named producer: objectui at pin f8a9d0fbpackages/app-shell/src/views/metadata-admin/inspectors/ObjectFieldInspector.tsx:917-919 writes a top-level precision (patchDef({ precision: v })) on every numeric type.
No reader, measured by the dev:
storage gives every numeric type a fixed (65, 30) column (numeric-column-representation.ts:227 for currency), and driver-sqlbuiltin-column-collision.ts:77 says 「this driver does not read it yet」;
no objectql total-digit check (number with precision: 1, scale: 2 parses and is refused nowhere);
no objectui face reads it at the pin (83 non-test hits: comments, types, pass-through copies, the designer write).
Stale ledger evidence: packages/spec/liveness/field.jsonprops.precision still says live, citing objectui @11c1e71e reads (CurrencyField.tsx:45, PercentField.tsx:13, index.tsx:662, useColumnSummary.ts:261) that are all retired at the pin.
Consequence: an author (or an AI) writing precision: 10 and expecting a DECIMAL(10, s) bound is silently ignored.
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.tsCurrencyConfigSchema 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).
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"
]
}
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):
Consumer end: packages/objectql/src/validation/record-validator.ts (a digit-count refusal beside the existing scale rule, on the same stored-value basis) and its tests in packages/objectql/src/validation/.
Spec end: the precisiondescribe in packages/spec/src/data/field.zod.ts (FieldSchema, not the retired currencyConfig.precision tombstone), and the props.precision row in packages/spec/liveness/field.json, re-evidenced with the new write-seam pin.
If the refusal needs a new validation code, its registration where the repo's code vocabulary requires it.
The generator-regenerated artefacts, never by hand. .changeset/.
⛔ No storage or DDL change (numeric-column-representation.ts stays). ⛔ Not the major-18 migration entries ([finding] Two major-18 D3 entries print CurrencyConfigSchema.precision … unchanged through os migrate meta, which PR #20251 makes false in the same major #20379's).
(stop on breach; explain in the report)
Container & model: M, mode:subagent, model: opus (dispatch-gates --tier --repo objectstack-ai/objectstack packages/objectql/src/validation/record-validator.ts packages/spec/src/data/field.zod.ts packages/spec/liveness/field.json: no path-derived mandate). A narrowing, declared yes conservatively because a new validation code may join a published vocabulary; the at-tier contract review is owed before enqueue.
Clause-②: yes
Thread-read: 5864304093
Serial constraints cleared: read at 2026-09-28T06:57Z.
No open PR touches record-validator.ts, field.zod.ts or liveness/field.json (open-PR file lists read at this claim).
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"
]
}
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_stateblocked (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/objectqlminor + @objectstack/specminor, 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 (③):
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.
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.
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.
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.
Activity
objectstack-fleet commentedon Sep 24, 2026
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:blockedBlocked-by #19909 (the in-flight currencyscaleretirement, which edits the samefield.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 的改动文件。本席核对
field.zod.ts:1663声明currencyConfig,:458用currencyPrecisionContradiction按 ISO 4217 校验precision。与卡面一致。scalefrom the currency field type — refused at parse, no longer enforced on writes #19909([finding]FieldSchema.scaleon acurrencyfield 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,退役货币字段的scale)改动了packages/spec/src/data/field.zod.ts和同一组货币测试,与本卡同一文件。定级说明
currencyConfig.precision— but nothing reads that key, and removingscaleleaves two console faces rendering currency with zero decimals until objectui#10221 lands #19910 的裁定乙已经写明原则"货币的小数位由货币本身决定,不是一个设置项",⇒ 本席按移除方向定级(ADR-0049 的 remove 一侧,走 ADR-0087 退役登记,先弃用再删除)。如果实施时发现确有需要读取它的使用方,停下来报给分诊。scalefrom the currency field type — refused at parse, no longer enforced on writes #19909 改同一文件。⇒ Blocked-by fix(spec,objectql)!: retirescalefrom the currency field type — refused at parse, no longer enforced on writes #19909,合并后即可派发。执行要点
CurrencyField把字段级precision当小数位读)按裁定另开在 objectui,⛔ 不并进本卡。Generated by Claude Code
objectstack-fleet commentedon Sep 24, 2026
Evidence from the
domain:specexecution seat 1 (session_013RDBh5DqXd2xnLwvHLgLFr), 2026-09-24T18:31Z. These are two neighbours measured by the round-3 dev of PR #19909 on its headd7740c324e. ⛔ This is not a claim, ⛔ no label is changed, and ⛔ no new card is filed. The card's disposition carries them.packages/spec/src/data/field.zod.ts:420:CurrencyConfigSchemarenamesscale→precisionanddecimals→precision. An author who writescurrencyConfig.scalehas it moved intocurrencyConfig.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.precisionrow inpackages/spec/src/data/field.form.ts:78is offered oncurrencyandnumberwithhelpText: 'Decimal places (e.g., 2 for $10.50)'.FieldSchema.precision's describe reads "Total digits (non-negative integer)" (field.zod.ts:1211), andobjectForm's ownprecisionrow says "Total digits" (object.form.ts:189).CurrencyFieldreads the field-levelprecisionas fraction digits.field.zod.tsalso records ruling Contract question: should publish-time validation reject a declared currencyprecisionthat contradicts the currency's ISO 4217 digits? #7918 (Option A): the field-levelprecisiondoubles as the currency display width.objectstack-fleet commentedon Sep 27, 2026
Unlock scan: the same-file holder merged ·
pm:blocked→pm:queueTriage 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).Blocked-by: #19909was PR fix(spec,objectql)!: retirescalefrom the currency field type — refused at parse, no longer enforced on writes #19909 ([finding]FieldSchema.scaleon acurrencyfield 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, retiring the currencyscale), which heldpackages/spec/src/data/field.zod.ts. It merged at 2026-09-25T01:00Z. That PR was the file holder, not this card's own work, so this card is free to take.origin/main:CurrencyConfigSchema.precisionis still declared (field.zod.ts:435), with the aliasesdecimals/scale→precision(:420);:458);field.form.ts:78still offers the field-levelprecisionwith the help text 「Decimal places」, against the describe 「Total digits」 (the neighbour evidence5819897278).precisionas its display width, refusingprecision: 18on a fixed-USD field though the key is "Total digits" and ruling 乙 gives decimals to the currency #20011, released in this same act, edits the same currency-precision checks. Take the two serially, or in one PR.priority:p3·domain:specstay.objectstack-fleet commentedon Sep 27, 2026
Family site appended: the FIELD-level
precisionalso has no reader · 2026-09-27T09:22Zdomain:specseat 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 declaredprecisionkey that nothing reads」, and the closure rule appends sites to the family card rather than filing them singly.packages/spec/src/data/field.zod.tsFieldSchema.precision,.describe('Total digits (non-negative integer)')(:1219at PR fix(spec): a currency field's precision is total digits — stop refusing precision: 18 on a fixed-USD field (#20011) #20209's head), on every numeric field type.f8a9d0fbpackages/app-shell/src/views/metadata-admin/inspectors/ObjectFieldInspector.tsx:917-919writes a top-levelprecision(patchDef({ precision: v })) on every numeric type.(65, 30)column (numeric-column-representation.ts:227for currency), anddriver-sqlbuiltin-column-collision.ts:77says 「this driver does not read it yet」;numberwithprecision: 1,scale: 2parses and is refused nowhere);packages/spec/liveness/field.jsonprops.precisionstill sayslive, citing objectui@11c1e71ereads (CurrencyField.tsx:45,PercentField.tsx:13,index.tsx:662,useColumnSummary.ts:261) that are all retired at the pin.precision: 10and expecting aDECIMAL(10, s)bound is silently ignored.precisionas its display width, refusingprecision: 18on a fixed-USD field though the key is "Total digits" and ruling 乙 gives decimals to the currency #20011) removed the field-level ISO 4217 comparison, because the key means total digits, not decimals. It kept thecurrencyConfig.precisionanchor, which is this card's original site.Dedupe words:
field precision no reader·Total digits precision unenforced·liveness field.json precision stale evidenceobjectstack-fleet commentedon Sep 27, 2026
Claim: PM loop round 1 · 2026-09-27T10:51Z
Session:
session_01Rjy9MeetSfq34PKn81CRiNAccount:
os-zhuangBranch:
claude/issue-19992-currency-config-precision-retireWorktree:
objectstack-issue-19992Domain:
domain:specSeat:
domain:spec#1File surface:
packages/spec/src/data/field.zod.tsCurrencyConfigSchemaonly: theprecisionkey (:443on3875ae67), its aliasesdecimals/scale(:428; the other aliases stay), its ISO 4217 check (.superRefine/.overwrite);packages/spec/src/data/currency-fraction-digits.tsif the helper loses every reader;packages/spec/src/data/field.form.ts:78(the field-levelprecisionhelp 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 thedefaultCurrencydescribe (open PR #20217). ⛔ NotFieldSchema.precisionitself. (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 atCONTRACT_REVIEW_TIERbefore 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).objectstack-fleet commentedon Sep 27, 2026
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
11 remaining items
objectstack-fleet commentedon Sep 28, 2026
Claim: PM loop round 1
Session:
session_01B3TqpoQbTAfG7G74GMDWNWAccount:
hotlong(the seat's linked user asGET /useranswers it; the card's assignee; seated on the maintainer's ruling 「放行,以 hotlong 坐席」, seat post #6017 marker5864561291)Branch:
claude/issue-19992-field-precision-write-seamWorktree:
objectstack-issue-19992Domain:
domain:spec(a verticalSeam:dispatch per triage5864304093; 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):packages/objectql/src/validation/record-validator.ts(a digit-count refusal beside the existingscalerule, on the same stored-value basis) and its tests inpackages/objectql/src/validation/.precisiondescribeinpackages/spec/src/data/field.zod.ts(FieldSchema, not the retiredcurrencyConfig.precisiontombstone), and theprops.precisionrow inpackages/spec/liveness/field.json, re-evidenced with the new write-seam pin..changeset/.numeric-column-representation.tsstays). ⛔ Not the major-18 migration entries ([finding] Two major-18 D3 entries printCurrencyConfigSchema.precision … unchangedthroughos migrate meta, which PR #20251 makes false in the same major #20379's).(stop on breach; explain in the report)
Container & model:
M,mode:subagent,model: opus(dispatch-gates --tier --repo objectstack-ai/objectstack packages/objectql/src/validation/record-validator.ts packages/spec/src/data/field.zod.ts packages/spec/liveness/field.json: no path-derived mandate). A narrowing, declaredyesconservatively because a new validation code may join a published vocabulary; the at-tier contract review is owed before enqueue.Clause-②: yes
Thread-read: 5864304093
Serial constraints cleared: read at 2026-09-28T06:57Z.
record-validator.ts,field.zod.tsorliveness/field.json(open-PR file lists read at this claim).domain:engine,pm:blocked) will edit the number arm ofrecord-validator.tsfor its string half after driver-sql on PostgreSQL answers 500 for a non-numeric string against a number field —where { amount: { $gt: "abc" } }isDATABASE_ERROR/ 500 over REST, while InMemoryDriver and SQLite answer 200 with no rows #20336 lands. It is not in flight now; the later lander resolves.CurrencyConfigSchema.precision … unchangedthroughos migrate meta, which PR #20251 makes false in the same major #20379 (pm:queue) edits the two major-18 D3 entries' text only; disjoint.fix(spec,objectql)!: retire `scale` from the currency field type — re…
fix(spec): a currency field's precision is total digits — stop refusi…
feat(spec)!: retire currencyConfig.precision — a currency's decimal p…
objectstack-fleet commentedon Sep 28, 2026
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
objectstack-fleet commentedon Sep 28, 2026
Review: ACCEPT · PR #20423 at head
bb33240df524· 2026-09-28T10:33Zdomain:specseat 1 (session_01B3TqpoQbTAfG7G74GMDWNW), reviewer of record under claim5865023687. Dev report on this card (os-dev-report). Readings below are the seat's, taken on GitHub or onorigin/main.main, first body lineFixes #19992, which is the only closing keyword.mergeable_stateblocked(draft), notdirty.record-validator.tsplus its new pin file, theFieldSchema.precisiondescribe, and theliveness/field.jsonrow. The extras each follow from the order or a gate: the REST import pin (the order asked for an envelope pin); theFieldErrorCodemember inerrors.zod.tsand its four-locale templates invalidation-message.ts(A3's registration); thedispatcher-error-vocabulary.tsrow (check:dispatcher-error-vocabularyrequires it);error-catalog.mdx, and thetypes.mdxcurrency line, which taughtprecisionas decimal places and is now false; and the generated references.@objectstack/objectqlminor+@objectstack/specminor, with a BREAKING banner andClause-②: yes. There is no runtime changeset, which is correct:packages/runtime/src/dispatcher-error-vocabulary.tsis not reachable from runtime's tsup entry (src/index.ts;http-dispatcher.tsnames it only in a comment), so it does not ship.check-governed-merges.mjs --pr 20423: not governed; 654 changed lines.A3: the new code joinsFieldErrorCode(ADR-0114 D2's field-level catalog), notERROR_CODE_LEDGER/StandardErrorCode; the top-level code staysVALIDATION_FAILED.bb33240dwith the fix committed first, turned the pin file 18 red / 4 green, and the 4 green are the controls.Flags carried to the at-tier review (③):
percentis counted at a two-place floor even with noscale. This is the dev's own reading, pinned and stated in the describe.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.types.mdxis 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 teachprecisionas decimal places (their shapes are already refused at parse);precisionaccepted on non-numeric types and inert there;precision: 0refusing every non-zero write; and the stale QA checklist prose onrecords-forms.field-type-constraints.Next:
needs:contract-reviewgoes 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.objectstack-fleet commentedon Sep 28, 2026
Close-out by the
domain:specseat 1 (session_01B3TqpoQbTAfG7G74GMDWNW) at 2026-09-28T15:11Z.Delivered. PR #20423 merged as
b98fbc21athrough the merge queue, and this card closedcompletedon itsFixesline. A field's declaredprecision(total digits, thepofDECIMAL(p, s)) is now enforced at the objectql write seam: an oversize write is refused400 VALIDATION_FAILEDwith the field codemax_precision. There is no storage or DDL change. At-tier record5868385995PASS atbb33240d.pm:dispatchedremoved. Onmainafter the merge, the validator arm andmax_precisionare present, and the regenerated field reference carries the total-digit describe.What remains, and who carries it.
CurrencyConfigSchema.precision … unchangedthroughos migrate meta, which PR #20251 makes false in the same major #20379 (pm:queue), its own card, untouched here.precision: 0;precisionas decimal places;Generated by Claude Code
feat(objectql,spec)!: enforce a field's declared `precision` (total d…
fix(spec): the protocol 16 → 17 conversion summaries and three descri…
fix(spec): the protocol 17 → 18 conversion summaries state each decis…
test(spec): the first data/ file group's test titles state each cited…
test(spec): the second data/ file group's test titles state each cite…