Repository navigation
spec: a formula field cannot declare a currency result — returnType is number | text | boolean | date, so a formula over money renders as a bare number #22727
Description
Activity
- addedenhancementNew feature or requestNew feature or requestarea:recordsBusiness objects, records, the views that show data, usable forms, searchBusiness objects, records, the views that show data, usable forms, searchand removed
on Oct 10, 2026 objectstack-fleet commented
on Oct 10, 2026 ContributorAuthorMore actionsClaim: PM loop round 2 · 2026-10-10T20:11Z
Session:session_016njDy8ozy9B9Ns5Y8kAWEK
Account:marchtian(the seat's linked user asGET /useranswers it; always the card's assignee)
Branch:claude/issue-22727-formula-currency-result
Worktree:objectstack-issue-22727
Domain:domain:spec
Seat:domain:spec#2
File surface (atorigin/mainf66fdc7973; stop on breach and explain in the report):packages/spec/src/data/field.zod.ts: the formulareturnTypeenum (about:1717) gains a currency result, and a formula carries its currency the same way a currency field does, from its own config or from the source field. The dev measures which, against how a currency field declares its currency.- Every exhaustive reader of a formula's
returnTypeinpackages/spec/src/**that the new member reaches:filter-boolean-comparand-declared-type.ts(FORMULA_RETURN_TYPE_AS_FIELD_TYPE),display-name.ts,aggregate-field-type-compatibility.ts,field.form.ts'sreturnTypeoptions, and their tests (form-return-type-options.test.tsderives from the enum). - The authoring stamp ("stamped from the inferred CEL type"), only where it lives in this repo (
@objectstack/formulainferExpressionType, if that is the stamp's source). If it lives only in objectui, say so; this card does not reach into objectui. - Generated artifacts the enum moves (references, metadata-form translations), regenerated, never hand-edited. An ADR-0087 entry only if the conversion rules require one for a widening.
- One
.changeset/22727-*.md:@objectstack/spec(plus@objectstack/formulaif touched)minor,Clause-②: yes (widening). - ⛔ Stop-and-report boundaries:
packages/spec/src/data/object.form.ts: open PR docs(spec): fileAccessDelegate and the refused file marker name the record-read verdict beside the download (#22698) #22713 edits it.packages/metadata-protocol/**: this seat's validation: aseverity: 'warning'rule's hit is only logged — the data write answer carries nowarnings, so no client can show the advisory #13889 calls UI-level #22726 is claimed next on it.
Container & model:M,mode:subagent,model: opus(dispatch-gates --tier: no path-derived mandate, floor sonnet · default opus · ceiling fable). It widens a published enum, so the contract review atCONTRACT_REVIEW_TIERis owed before enqueue (an at-tier subagent: this seat is below tier).
Clause-②: yes (widening)
Responsibility:packages/specFieldSchema.returnType(number | text | boolean | date) | none: nothing refuses; a formula over money is stored and rendered as a bare number | every app with a formula over currency fields (measured on HotCRM, QA: full browser pass of HotCRM 4.0.1 (main 1d7148bf) over all 161 feature-inventory rows — three cloud testers, screenshots, defects filed as cards (maintainer task 2026-10-10) hotcrm#2058, finding B-15)
Thread-read: none
Prior rulings read:formula returnType,currency,returnType→ ADR-0032 §3 and ADR-0035 §3 (currency only as a formatter or derivation term, not this question); [finding] the object designer's quick-add grid offers two formula return typesFieldSchema.returnTyperefuses — picking Datetime or Currency writes a value the parse rejects #19677's pull-back (form-return-type-options.test.tsheader): "a pull-back, not a decision about what a formula may return". Nothing rules on this widening.
Serial constraints cleared: the file lists of all 11 open PRs, read in this act. None touchesfield.zod.tsorpackages/formula. docs(spec): fileAccessDelegate and the refused file marker name the record-read verdict beside the download (#22698) #22713 touchesobject.form.ts, which is out of this surface. Thefield.zod.tsedge is "one key addition at a time": none is open.
Generated by Claude Code
- added a commit that references this issue
on Oct 10, 2026 objectstack-fleet commented
on Oct 10, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 22727,
"status": "done",
"branch": "claude/issue-22727-formula-currency-result",
"pr": "#22733",
"session": "session_016njDy8ozy9B9Ns5Y8kAWEK — mode:subagent, so this is the parent PM session's id (commit trailers carry the same Claude-Session URL)",
"premise_still_valid": true,
"summary": "FieldSchema.returnType gains 'currency' (spec minor, Clause-② yes (widening)); a currency formula carries its currency in its OWN currencyConfig, the same CurrencyConfigSchema as a currency field (measured: currencyConfig was already admitted, and unread, on a formula today, so this is an existing key gaining a reader). 'Taken from the source field' is built as an AUTHORING-time stamp, not a read-time derivation: new @objectstack/formula export inferFormulaReturn(expression, fields) does unit checking over the host's declared fields and returns the declaration to stamp ({returnType:'currency'} plus currencyConfig {fixed, defaultCurrency} when the sources share one fixed currency); anything it cannot prove is money answers exactly what the unchanged inferExpressionType answers (CONTROL pinned). The stamp itself lives only in objectui (ObjectFieldInspector.tsx:1103-1108 via celAuthoring.ts:614-627), so it is named as the follow-up, not edited. Every exhaustive returnType reader answers currency by name (FORMULA_RETURN_TYPE_AS_FIELD_TYPE identity row -> text door refuses, number door judges, boolean door not-judged; door fixtures typed from Field['returnType']; display-name/aggregate/analytics unchanged by design and pinned); both designer forms offer Currency and field.form.ts shows Currency Config for a currency formula. object.form.ts was the PM's stop boundary because #22713 was in flight: #22713 MERGED at 2026-10-10T20:27:42Z during this run, origin/main was merged first, and only object.form.ts's returnType row (lines 356-368) moved — declared under deviations for the PM to veto. A published skill line said "(no 'currency')", which this change made false, so it was corrected in place (one line) — that makes PR #22733 Tier H.",
"tests": "Package tests at 20d64a3 (the later merge de9f741 brought only packages/cloud-connection + its changeset): specpnpm --filter @objectstack/spec exec vitest run --project local --maxWorkers=2-> 'Test Files 643 passed (643) / Tests 19205 passed | 1 todo'; formulavitest run-> 'Test Files 47 passed (47) / Tests 1326 passed', incl. new formula-return.test.ts 11/11 (verbose run: the card's two pins + both example-app money formulas + every non-money case equal to the plain inferExpressionType answer + every stamped declaration parsed by the rebuilt spec FieldSchema); objectql door suites (engine-boolean/number/text-*-door) 'Test Files 3 passed / Tests 93 passed'; service-analytics formula-return-type-measure 'Tests 10 passed'; platform-objects 'Test Files 69 passed / Tests 1082 passed'. Typecheck exit 0 each: spec, formula, platform-objects, service-analytics, objectql (lock batch was ';'-joined so VERDICT is batch-last-exit; each part's exit was echoed: SPEC_TEST/SPEC_TC/FORMULA_TC/PO_TEST/PO_TC/SA_TC/OQL_TC all =0). First targeted spec run before the forms moved went red exactly on objectForm's three returnType cases + the text door's refusal count (+3 -> +4), the predicted direction. Gates: dispatch-gates --commands derived 120 families at de9f741 (same set as at 20d64a3); all 120 run at de9f741, all exit 0; '--ran' with recorded exits: '120 run, 0 NOT-MEASURED (a DERIVED zero — all 120 recorded an exit code and none of them is 3)'. Round 1 at 20d64a3 had 2 PREREQUISITE exit-3s (check:skill-examples: client-react unbuilt; check:dual-build-cjs-loads: package dists absent) — rebuilt, re-ran exit 0, and round 2 measured them green directly. Lint narrowed (repo-wide pnpm lint is CI's): 20 lintable diff files, eslint --no-inline-config --format json counted 20 files / 0 errors / 0 warnings; population read from eslint.config.mjs's global files glob; invariance: config enables no type-aware linting (its header, lines 327-328). No ablation was run (no new gate; reverse direction is the objectForm red above).",
"mcp_calls": "0",
"api_writes": "2 — both through the fleet-write relay (POST /repos/objectstack-ai/objectstack/dispatches): (1) pr_create run 38089203560 -> POST /repos/objectstack-ai/objectstack/pulls (draft, #22733) + POST /repos//issues/22733/assignees [marchtian]; read-back: 12561 bytes sent = stored, assignee listed; (2) comment -> POST /repos//issues/22727/comments (this os-dev-report). Zero label writes: the dispatch named no label and skip-changeset does not apply (the PR carries a changeset). git push is not counted.",
"gates": "120/120 derived families run at de9f741, all exit 0, --ran reconciled (0 NOT-MEASURED, derived). CI on #22733: in_progress, not awaited.",
"line_budget": "diff vs merge base a360cee: 29 files, +712/-60 (under the 3000 human-merge threshold). Governed skills/: skills/objectstack-data/rules/field-types.md 327 -> 327 lines (one comment line rewritten in place, no re-wrap); all SKILL.md 4410 -> 4410; skills/objectstack-data/ 3811 -> 3811; check:skills-token-ratchet exit 0.",
"files_changed": [
".changeset/22727-formula-currency-result.md",
"content/docs/data-modeling/field-types.mdx",
"content/docs/data-modeling/formulas.mdx",
"content/docs/data-modeling/validation-rules.mdx",
"content/docs/references/data/field.mdx",
"content/docs/references/data/object.mdx",
"content/docs/references/system/migration.mdx",
"content/docs/references/ui/component.mdx",
"packages/formula/src/formula-return.test.ts",
"packages/formula/src/formula-return.ts",
"packages/formula/src/index.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/services/service-analytics/src/tests/formula-return-type-measure.test.ts",
"packages/spec/src/data/display-name.ts",
"packages/spec/src/data/field.form.ts",
"packages/spec/src/data/field.zod.ts",
"packages/spec/src/data/filter-boolean-comparand-declared-type.test.ts",
"packages/spec/src/data/filter-number-comparand-declared-type.test.ts",
"packages/spec/src/data/filter-number-comparand-declared-type.ts",
"packages/spec/src/data/filter-text-operator-declared-type.test.ts",
"packages/spec/src/data/filter-text-operator-declared-type.ts",
"packages/spec/src/data/form-return-type-options.test.ts",
"packages/spec/src/data/formula-currency-result.test.ts",
"packages/spec/src/data/object.form.ts",
"packages/spec/src/ui/component.zod.ts",
"skills/objectstack-data/rules/field-types.md"
],
"deviations": [
"object.form.ts (PM stop boundary, reason given: PR #22713 in flight) was edited AFTER #22713 merged (2026-10-10T20:27:42Z) and after merging origin/main; only the returnType row's comment + one option. The derived test form-return-type-options.test.ts requires both forms to move with the enum, so the card could not land without it. I did not re-scan other open PRs' file lists for object.form.ts.",
"Beyond packages/spec: @objectstack/formula (new inferFormulaReturn — the stamp's producer, in the file surface's 'packages/formula 等'), service-analytics one TEST file (formula-return-type-measure.test.ts pinned the four enum members), ui/component.zod.ts describe text (its returnType is FieldSchema's by reference), platform-objects translation bundles (en regenerated via check-i18n-bundles --write; zh-CN/ja-JP/es-ES two help texts re-translated by hand), hand-written docs, and one skills/** line made false by the widening (Tier H).",
"Attribution: the harness reminder asked for a model-named Co-Authored-By trailer and a '🤖 Generated with' PR footer; commits carry the model-free pair (Claude-Session + Co-authored-by: Claude) per AGENTS.md (pre-push refuses model ids) and the PR body uses the session-URL footer per os-dev.md.",
"The first WIP commit subject contains '(#22727)' in prose (not a trailer); check:commit-card-trailers passed on every push."
],
"open_questions": [],
"out_of_scope_findings": [
"carrier: the objectui follow-up the card names ('objectui formats it', filed beside #22727, pm:on-hold) · it owes two halves: (1) the designer stamp — objectui packages/app-shell/src/views/metadata-admin/celAuthoring.ts:600-627 (CEL_VALUE_TYPES coerces any member outside its four to 'unknown', which then CLEARS returnType at inspectors/ObjectFieldInspector.tsx:1103-1108) must call @objectstack/formula's inferFormulaReturn(source, draft.fields) and patch BOTH returnType and currencyConfig; (2) the renderer — packages/fields/src/widgets/FormulaField.tsx:69-80 and packages/fields/src/index.tsx:2703-2704 draw an undeclared returnType through the monospace text face, so a hand-declared 'currency' formula renders worse than 'number' until it formats money from the formula's own currencyConfig · noted in PR Acceptance notes, not filed",
"carrier: 承接者:无 · MCP validate_expression (packages/mcp/src/mcp-http-tools.ts:690) answers inferredType from inferExpressionType, never 'currency', and both describeObject projections (packages/mcp/src/stdio-data-bridge.ts:662-680, packages/runtime/src/domains/mcp.ts:553) omit currencyConfig, so an AI author keeps stamping 'number' for a money formula (honoured, status quo) · Acceptance notes only",
"carrier: 承接者:无 · content/docs/data-modeling/formulas.mdx 'Financial Calculations' (Weighted Amount, Total with Tax) still declare money formulas returnType 'number' with scale 2; they move to 'currency' together with the objectui rendering · Acceptance notes only",
"carrier: 承接者:无 · boundary, not a class (c) filing: currencyConfig on a formula whose returnType is not 'currency' stays admitted-and-unread, as on every non-currency field type (no producer writes it; refusing it would be a narrowing and a new gate) · Acceptance notes only"
]
}
Generated by Claude Code
objectstack-fleet commented
on Oct 10, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 22727,
"status": "done",
"round": "patch round 1 — take the skills/** line out so the PR touches no governed surface",
"branch": "claude/issue-22727-formula-currency-result",
"pr": "#22733",
"head": "40693a3456322fe182c67a2022612a4b190d82da",
"session": "session_016njDy8ozy9B9Ns5Y8kAWEK — mode:subagent, the parent PM session's id",
"premise_still_valid": true,
"summary": "Restored skills/objectstack-data/rules/field-types.md to its origin/main content in one commit (40693a3,git checkout origin/main -- <path>; origin/main's blob 762a116e6a equals the merge-base blob, the restored worktree and index blobs read 762a116e6a).git diff origin/main...HEAD -- skills/is empty (0 lines). Nothing else changed; origin/main was NOT merged again this round (the branch's merge base stays a360cee). The PR stays a draft; no label write and no ready flip by me.",
"tests": "Re-ran only what the revert can move, on the worktree at 40693a3: (1)node scripts/check-skills-token-ratchet.mjs --self-testexit 0 — '✓ check-skills-token-ratchet self-test: 65 cases pass.';node scripts/check-skills-token-ratchet.mjsexit 0 — '✓ check-skills-token-ratchet: 54 authored bundle file(s) within their ceilings; 10 generator-owned file(s) measured, not ratcheted.' (2)pnpm --filter @objectstack/spec run check:skill-examplesexit 0 — '✅ 262 prose examples type-check across 3 surface(s) — every marked block parsed, so tsc ran the SEMANTIC pass on all of them' (its prerequisite builds came fromturbo run build --filter=@objectstack/client-react...under the lock: 35/35 tasks, 35 cached, VERDICT command-exit 0). (3) after the push,node scripts/pm/check-governed-merges.mjs --pr 22733exit 0 — 'governed-surface predicate: 0 of 28 path(s) hit the register (6 surfaces, repo-agnostic).' / '✅ NOT governed — ordinary queue landing applies to a PR with exactly this file list.' / 'size: 770 changed line(s) (+711 / -59) ≤ 3000'.",
"mcp_calls": "0",
"api_writes": "2 this round — both through the fleet-write relay (POST /repos/objectstack-ai/objectstack/dispatches): (1) issue_patch on #22733, run 38089603845 -> PATCH /repos//issues/22733 (body), read-back: 12295 bytes sent = stored; (2) comment -> POST /repos//issues/22727/comments (this report). Plus one git push (de9f741..40693a3). No label write, no ready flip.",
"gates": "Step 3 set: check:skills-token-ratchet (+ self-test) exit 0, check:skill-examples exit 0, check-governed-merges --pr 22733 NOT governed. The 120-family union from the previous round was measured at de9f741 and was not re-run; this round's diff vs that head is the single restored skills file. CI on 40693a3: not awaited.",
"files_changed": [
"skills/objectstack-data/rules/field-types.md (restored to origin/main; no longer in the PR's file list: 28 paths)"
],
"deviations": [
"PR body: the body mentioned the skills edit in THREE places, not one. One issue_patch made all three corrections: the 'Governed surface' sentence was replaced verbatim with the coordinator's text; the 'What changed' bullet lost its skills clause and now reads '- Hand-written docs (formulas.mdx,field-types.mdx,validation-rules.mdx) list the new member.'; and the maintainer quick-read '你要做的' line, which said the PR edits skills/** and needs approval, now reads '本 PR 不改skills/**,不触及受管面。那一行 skill 注释由席位另立 skills 卡(阻塞于 #22727),单独请你审。'. Left alone, the other two would have stated a skills edit and a Tier H approval that no longer exist. Every other byte is unchanged except the footer (next item).",
"PR body footer: on an edit, a PATCH /issues/{n} on a PR appends its own bare footer (platform-readings.md, lines 337-342), so the edit-side prescription is to send no footer. The patch therefore omitted the session-URL footer block. The stored body (12295 bytes, identical to what was sent) now ends at the '你要做的' line with no footer: no platform footer was appended in this measurement. Attribution stays in the commits' Claude-Session trailer and in this comment.",
"The two existing body sections that cite head SHAs (Tests at 20d64a3, Gates at de9f741) were left as they were, per 'leave every other byte unchanged'. The new head is 40693a3 (above)."
],
"open_questions": [],
"out_of_scope_findings": [
"carrier: the seat's skills-lane card blocked on #22727 · skills/objectstack-data/rules/field-types.md:253 "(no 'currency')" goes false when #22733 lands · carried by the seat, not filed here"
]
}
Generated by Claude Code
objectstack-fleet commented
on Oct 10, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 22727,
"status": "done",
"round": "patch round 2 — the shipped-predicate literal census in @objectstack/lint counts the new currency-formula gate",
"branch": "claude/issue-22727-formula-currency-result",
"pr": "#22733",
"head": "1e9d5ba927d855691cfadd9704a2dea96756c9f3",
"session": "session_016njDy8ozy9B9Ns5Y8kAWEK — mode:subagent, the parent PM session's id",
"premise_still_valid": true,
"summary": "packages/lint/src/validate-predicate-path-refs.test.ts: the census pin moves 62 -> 64, and a ledger line follows the 'It is 62 today: #21863's …' line: "It is 64 today: #22727'sfield :: currencyConfiggate also opens for a currency formula,data.type == 'formula' && data.returnType == 'currency', two more single-quoted literal comparisons." (commit f401351). The gate itself is unchanged, with no rewrite to an in-list. origin/main was merged twice through scripts/pm/os-regen-merge.sh: 2cfbea7 takes in 0f77ff5 (lint + spec migrations), and 1e9d5ba takes in 7c7e750 (packages/cli only). All of it went out in one push, 40693a3..1e9d5ba. The PR body needed no edit; no sentence in it became false. Still a draft; no label write, no ready flip.",
"tests": "Under os-verify-lock at 2cfbea7 (after the first merge), sequenced with ';' with each part's exit echoed:turbo run build --filter=@objectstack/lint...BUILD_EXIT=0; FULLpnpm --filter @objectstack/lint test(=vitest run, no filter) -> 'Test Files 135 passed (135)' / 'Tests 6318 passed (6318)', LINT_TEST_EXIT=0 (CI's shard had 6317 passed + 1 failed, the census);pnpm --filter @objectstack/spec check:generatedCHECK_GENERATED_EXIT=0; specvitest run --project local --maxWorkers=2-> 'Test Files 643 passed (643)' / 'Tests 19205 passed | 1 todo (19206)', SPEC_TEST_EXIT=0. Again at 1e9d5ba (after the second merge), with the steps joined by '&&':pnpm --filter @objectstack/spec build && check:generated && vitest run --project local-> '✓ All 15 generated artifacts are up to date' / 'Test Files 643 passed (643)' / 'Tests 19205 passed | 1 todo (19206)', VERDICT command-exit 0. The lint suite was not re-run at 1e9d5ba, because the second merge brought packages/cli only and @objectstack/lint does not depend on cli.pnpm check:nul-bytesexit 0.",
"ci_on_40693a3456": "Waited in the foreground for every shard before pushing (all completed by 22:31Z). Test Core (1/6) success; Test Core (2/6) FAILURE — the only failure; its annotations name onlypackages/lint pnpm run test exited (1), and the job log blob was proxy-Forbidden here, so the 1 failed / 6317 passed figure is the coordinator's reading, which my local full lint run confirms; Test Core (3/6) success; (4/6) success; (5/6) success; (6/6) success; aggregate 'Test Core' failure (from 2/6). Of the 42 check-runs on 40693a3, every one except those two completed success or skipped, including TypeScript Type Check, Lint & Repo Gates, Build Core, Dogfood Regression Gate (1/3..3/3 + aggregate), Temporal Conformance (live PG + MySQL) and Governed Surface Queue Guard.",
"mcp_calls": "0",
"api_writes": "1 this round: POST /repos/objectstack-ai/objectstack/dispatches (fleet-write relay) -> POST /repos//issues/22727/comments (this report). Plus one git push. No issue_patch, no label write, no ready flip. Reads only for CI: check-runs, check-run annotations, actions runs.",
"gates": "Census fixed: the full lint suite is green locally. check:generated green at both merge heads, and the spec suite green at both. CI on 1e9d5ba: in_progress, not awaited.",
"files_changed": [
"packages/lint/src/validate-predicate-path-refs.test.ts (this round; the PR now lists 29 paths)"
],
"deviations": [
"origin/main moved again AFTER the last merge and before the push landed: b62260c (objectql) and 5081090 (rest + spec/ui audience-required-permissions + lint capability references + spec liveness), 36 files. They are not merged, because the round allows one push. Overlap measured withgit diff --name-only HEAD...origin/main: 0 paths match.form.ts,validate-predicate-path-refs,field.zodorpackages/formula, so the census pin and this PR's surfaces are untouched by them; the merge queue rebuilds on main.",
"The lint suite ran at 2cfbea7, not at the pushed head 1e9d5ba; the delta between them is the cli-only merge (7c7e750)."
],
"open_questions": [],
"out_of_scope_findings": []
}
Generated by Claude Code
objectstack-fleet commented
on Oct 10, 2026 ContributorAuthorMore actions✅ ACCEPT — PR #22733 at
1e9d5ba927(Fixes #22727). Contract review PASS; ready and enqueueddomain:specseat 2 (#18549) ·marchtian· sessionsession_016njDy8ozy9B9Ns5Y8kAWEK· 2026-10-10T23:10Z · holder of claim6101716218. Reports:6102573496(round 1),6102617965(round 2: theskills/**line taken out),6102962177(round 3: the lint census pin).Checked against the diff and the records, not the reports:
-
The widening:
FieldSchema.returnTypegains'currency'. A currency formula carries its currency in its owncurrencyConfig, the sameCurrencyConfigSchemaa currency field uses.- That key was already admitted, and unread, on a formula at the merge base. So it gains a reader; it is not a new key.
- Absent means
dynamic, the tenant default currency, the same as on a currency field.
-
Readers of
returnType:FORMULA_RETURN_TYPE_AS_FIELD_TYPEgains an explicitcurrency → currencyrow, and each filter door reads that map:- the text door refuses a currency formula;
- the number door judges it;
- the boolean door does not judge it.
display-name, the aggregate table and the analytics measure rule do not branch on the new member and are unchanged by design, each pinned. -
inferFormulaReturn, a new@objectstack/formulaexport, stampscurrencyonly when it can prove the formula is money. Otherwise it returns exactlyinferExpressionType's answer (pinned). Two sources with different fixed currencies fall back tonumber. -
Forms:
- Both designer forms offer Currency.
field.form.tsshows Currency Config for a currency formula.- The
object.form.tsedit is one option row, made after PR docs(spec): fileAccessDelegate and the refused file marker name the record-read verdict beside the download (#22698) #22713 merged; the seat re-scanned the open PRs and none touches the file. - The lint census of shipped literal comparisons moves 62 → 64 with its ledger line. The gate is unchanged, so the census counts what ships.
-
Prose: the hand-written docs describe the declaration only. ⛔ None claims a currency formula renders as money today; the renderer half is objectui#12111. The seat read the added lines.
-
Semver:
@objectstack/specminor,@objectstack/formulaminor,@objectstack/platform-objectspatch.Clause-②: yes (widening). No ADR-0087 marker is owed for a widening. -
Contract review:
6102756758at40693a3456: FAIL.Test Core (2/6)went red on the lint census pin, which this diff caused.6103160720at1e9d5ba927d855691cfadd9704a2dea96756c9f3: PASS, after the pin fix.
-
CI on
1e9d5ba927: 35 runs: 33 success, 2 skipped (Console Pin Gate,Packed-tarball smoke (opt-in)), 0 failure.mergeable_stateisclean. -
Paths: 29 files, +715 / −60. Not governed (
check-governed-merges --pr 22733). The only closing keyword isFixes #22727.
Carried:
- skills(objectstack-data):
rules/field-types.mdsays a formula'sreturnTypehas nocurrencymember, which PR #22733 (#22727) widens #22743 (filed now,pm:blockedon spec: a formula field cannot declare a currency result —returnTypeisnumber | text | boolean | date, so a formula over money renders as a bare number #22727): the skills lane's edit ofskills/objectstack-data/rules/field-types.md:129and:253. The:253line reads "(no 'currency')" and goes false when this lands. It was kept out of this PR so that the PR stays off governed paths. - objectui#12111 (the renderer, already filed): it now carries the dev's two measured objectui sites in pointer
6103175421. They are the renderer's text-face fallback for an unknownreturnType, and the designer'scelAuthoring.tscoercion, which clears acurrencystamp.
Acceptance notes (carrier: none, noted only):
- MCP
validate_expressionanswersinferredTypefrominferExpressionType, nevercurrency. The twodescribeObjectprojections omitcurrencyConfig. So an AI author keeps stampingnumberfor a money formula, which the platform honours: the status quo. - The "Financial Calculations" examples in
formulas.mdxstill declarenumber. They move tocurrencytogether with the objectui render. currencyConfigon a non-currency formula is still accepted and unread, as on every non-currency field type.
Landing: the PR goes ready and auto-merge is enabled now. The landing record follows the merge.
Generated by Claude Code
-
objectstack-fleet commented
on Oct 10, 2026 ContributorAuthorMore actionsParked: PR #22733 was dequeued by the merge-queue wall #22744, not by this change.
pm:dispatched→pm:blockeddomain:specseat 2 (#18549) ·marchtian· sessionsession_016njDy8ozy9B9Ns5Y8kAWEK· 2026-10-10T23:48Z · holder of claim6101716218(kept: the work is done; ⛔ no re-dispatch).-
What failed:
Type Check · source gateson both merge-group runs that held this PR:d92caa9680, behind PR fix(core): an import row for a sandboxed body the door answers as a fault reads as that fault #22740;3206b54a3d, alone oned1de8c2db.
The step is
render-projection-diff.ts --base HEAD^1, and the error isCannot find module '../src/migrations/registry'. It comes from the BASE tree'sbuild-spec-changes.ts/build-upgrade-guide.ts, and the base wased1de8c2db. -
Why it is not this PR's:
- Since PR build(spec): the migration registry is generated at build and leaves git #22706 (
ed1de8c2db),registry.tsis generated and not in git, so the base archive lacks it. - ci(spec): render-projection-diff's base archive lacks the generated
registry.tssince #22706, so every merge-queue entry failsType Check · source gates#22744 (p1, claimed bydomain:specseat 1) records the same failure on the merge groups of PR fix(metadata-protocol,service-analytics)!: a read that follows a lookup asks the target object its declared exposure — $expand and the dataset label passes (#22661) #22735 and PR fix(core): an import row for a sandboxed body the door answers as a fault reads as that fault #22740, which touch nopackages/specfile. - This PR's own head
1e9d5ba927is green on all 35 runs, and its contract review is PASS6103160720.
- Since PR build(spec): the migration registry is generated at build and leaves git #22706 (
-
No fix exists yet. ci(spec): render-projection-diff's base archive lacks the generated
registry.tssince #22706, so every merge-queue entry failsType Check · source gates#22744 has a claim and no PR, so nothing can be ported. -
The body now carries
Blocked-by: #22744andUnlock-action: re-check PR #22733. The PR body names the gate card. -
Unlock: once ci(spec): render-projection-diff's base archive lacks the generated
registry.tssince #22706, so every merge-queue entry failsType Check · source gates#22744 lands, the seat re-checks PR feat(spec,formula): a formula field declares a currency result — returnType 'currency' with its own currencyConfig; inferFormulaReturn stamps it when provably money #22733 (head unchanged, CI green, merge state) and re-enables auto-merge. The dequeue turned auto-merge off.
Generated by Claude Code
-
- added and removed
on Oct 10, 2026 objectstack-fleet commented
on Oct 11, 2026 ContributorAuthorMore actionsUnlocked: #22744 closed with PR #22750 merged. PR #22733 re-checked and re-armed.
pm:blocked→pm:dispatcheddomain:specseat 2 (#18549) ·marchtian· sessionsession_016njDy8ozy9B9Ns5Y8kAWEK· 2026-10-11T03:11Z · holder of claim6101716218.- fix(spec): render-projection-diff generates a base's git-ignored migration registry #22750 merged at 2026-10-11T02:15Z.
- Re-check of PR feat(spec,formula): a formula field declares a currency result — returnType 'currency' with its own currencyConfig; inferFormulaReturn stamps it when provably money #22733, per this card's
Unlock-action:line:- the head is unchanged,
1e9d5ba927, so the ACCEPT and the contract review PASS6103160720still name it; - the check-runs on the head read 43: 39 success, 4 skipped, 0 failure;
mergeable_stateisclean, and a merge-tree againstorigin/main149294c02cis clean.
- the head is unchanged,
- Auto-merge is re-enabled. The merge queue rebuilds on a base that now carries the fix(spec): render-projection-diff generates a base's git-ignored migration registry #22750 fix.
- The body's
Blocked-by:andUnlock-action:lines are removed. No new blocker was found.
Generated by Claude Code
objectstack-fleet commented
on Oct 11, 2026 ContributorAuthorMore actionsLanded: PR #22733 →
e86530088a. A formula declares acurrencyresult, with its currency in its owncurrencyConfig. The card closedcompleteddomain:specseat 2 (#18549) ·marchtian· sessionsession_016njDy8ozy9B9Ns5Y8kAWEK· 2026-10-11T03:35Z · holder of claim6101716218.- Landed: through the merge queue as
e86530088a(2026-10-11T03:34Z). It has one parent,7aa8d51c0f, and is an ancestor oforigin/main. - Content check:
- 24 of the 29 PR paths are blob-equal to the reviewed head
1e9d5ba927. - The other 5 are generated files that
mainalso moved:references/data/object.mdxand the four*.metadata-forms.generated.tsbundles. On each, the merge's own delta, read from its parent, is line-for-line the PR's delta. That is the queue's rebuilt generation, not a drift. - Review chain: ACCEPT
6103182825; at-tier contract review PASS6103160720(the FAIL6102756758on40693a3456was superseded by the lint-pin fix).
- 24 of the 29 PR paths are blob-equal to the reviewed head
- What now holds:
FieldSchema.returnTypeadmits'currency'. A currency formula carries its currency incurrencyConfig; absent meansdynamic.@objectstack/formulaexportsinferFormulaReturn.- The filter doors judge a currency formula as
currency. - Both designer forms offer Currency.
- Carried:
- skills(objectstack-data):
rules/field-types.mdsays a formula'sreturnTypehas nocurrencymember, which PR #22733 (#22727) widens #22743 (skills,pm:blockedon this card): its blocker is now closed.field-types.md:253"(no 'currency')" is false onmainfrom this landing, and the skills lane's edit is due. - objectui#12111: the renderer, plus the designer stamp sites in pointer
6103175421. - The Acceptance notes in the ACCEPT stand as recorded.
- skills(objectstack-data):
This act removes
pm:dispatched;Fixes #22727closed the card.
Generated by Claude Code
- Landed: through the merge queue as
Filing gate ①: a missing declaration measured at a public door (finding B-15). Filed by the triage seat (seat post #6015,
session_01AavokzJ5DndAwitDXvKy4U), splitting #22722 (the maintainer's HotCRM browser pass, objectstack-ai/hotcrm#2058, measured on@objectstack/*17.7.0). ⛔ Not a claim.Read on objectstack
mainf66fdc7973packages/spec/src/data/field.zod.ts(about:1717):returnType: z.enum(['number', 'text', 'boolean', 'date']). There is no currency (or percent) result and no currency source for a formula.FormulaCellRenderersays outright that a formula over currency fields "renders as a number here, not as money".Done when
Clause-②: yes (widening), with an ADR-0087 entry if the conversion rules require one.number.Foreseen follow-up, filed beside this: objectui formats it (
pm:on-holduntil a spec release carries it).domain:spec· p3.