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
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
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 mainf66fdc7973
packages/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.
objectui's FormulaCellRenderer says outright that a formula over currency fields "renders as a number here, not as money".
Done when
A formula declares a currency result, with the currency taken from the field's own config or from the source field, the same way a currency field carries it. Authoring stamps it when the inferred type is provably money.
Clause-②: yes (widening), with an ADR-0087 entry if the conversion rules require one.
Pins:
a formula over a currency field declares the currency result;
CONTROL: a numeric formula stays number.
Foreseen follow-up, filed beside this: objectui formats it (pm:on-hold until a spec release carries it).
Claim: PM loop round 2 · 2026-10-10T20:11Z
Session: session_016njDy8ozy9B9Ns5Y8kAWEK
Account: marchtian (the seat's linked user as GET /user answers 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 (at origin/mainf66fdc7973; stop on breach and explain in the report):
packages/spec/src/data/field.zod.ts: the formula returnType enum (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 returnType in packages/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's returnType options, and their tests (form-return-type-options.test.ts derives 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/formula if touched) minor, Clause-②: yes (widening).
os-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): spec pnpm --filter @objectstack/spec exec vitest run --project local --maxWorkers=2 -> 'Test Files 643 passed (643) / Tests 19205 passed | 1 todo'; formula vitest 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"
]
}
os-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-test exit 0 — '✓ check-skills-token-ratchet self-test: 65 cases pass.'; node scripts/check-skills-token-ratchet.mjs exit 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-examples exit 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 from turbo 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 22733 exit 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"
]
}
os-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's field :: currencyConfig gate 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; FULL pnpm --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:generated CHECK_GENERATED_EXIT=0; spec vitest 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-bytes exit 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 only packages/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 with git diff --name-only HEAD...origin/main: 0 paths match .form.ts, validate-predicate-path-refs, field.zod or packages/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": []
}
✅ ACCEPT — PR #22733 at 1e9d5ba927 (Fixes #22727). Contract review PASS; ready and enqueued
domain:spec seat 2 (#18549) · marchtian · session session_016njDy8ozy9B9Ns5Y8kAWEK · 2026-10-10T23:10Z · holder of claim 6101716218. Reports: 6102573496 (round 1), 6102617965 (round 2: the skills/** line taken out), 6102962177 (round 3: the lint census pin).
Checked against the diff and the records, not the reports:
The widening:FieldSchema.returnType gains 'currency'. A currency formula carries its currency in its own currencyConfig, the same CurrencyConfigSchema a 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_TYPE gains an explicit currency → currency row, 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/formula export, stamps currency only when it can prove the formula is money. Otherwise it returns exactly inferExpressionType's answer (pinned). Two sources with different fixed currencies fall back to number.
Forms:
Both designer forms offer Currency.
field.form.ts shows Currency Config for a currency formula.
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:
6102756758 at 40693a3456: FAIL. Test Core (2/6) went red on the lint census pin, which this diff caused.
6103160720 at 1e9d5ba927d855691cfadd9704a2dea96756c9f3: 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_state is clean.
Paths: 29 files, +715 / −60. Not governed (check-governed-merges --pr 22733). The only closing keyword is Fixes #22727.
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 unknown returnType, and the designer's celAuthoring.ts coercion, which clears a currency stamp.
Acceptance notes (carrier: none, noted only):
MCP validate_expression answers inferredType from inferExpressionType, never currency. The two describeObject projections omit currencyConfig. So an AI author keeps stamping number for a money formula, which the platform honours: the status quo.
The "Financial Calculations" examples in formulas.mdx still declare number. They move to currency together with the objectui render.
currencyConfig on 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.
Parked: PR #22733 was dequeued by the merge-queue wall #22744, not by this change. pm:dispatched → pm:blocked
domain:spec seat 2 (#18549) · marchtian · session session_016njDy8ozy9B9Ns5Y8kAWEK · 2026-10-10T23:48Z · holder of claim 6101716218 (kept: the work is done; ⛔ no re-dispatch).
What failed:Type Check · source gates on both merge-group runs that held this PR:
The step is render-projection-diff.ts --base HEAD^1, and the error is Cannot find module '../src/migrations/registry'. It comes from the BASE tree's build-spec-changes.ts / build-upgrade-guide.ts, and the base was ed1de8c2db.
Landed: through the merge queue as e86530088a (2026-10-11T03:34Z). It has one parent, 7aa8d51c0f, and is an ancestor of origin/main.
Content check:
24 of the 29 PR paths are blob-equal to the reviewed head 1e9d5ba927.
The other 5 are generated files that main also moved: references/data/object.mdx and the four *.metadata-forms.generated.ts bundles. 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 PASS 6103160720 (the FAIL 6102756758 on 40693a3456 was superseded by the lint-pin fix).
What now holds:
FieldSchema.returnType admits 'currency'. A currency formula carries its currency in currencyConfig; absent means dynamic.
@objectstack/formula exports inferFormulaReturn.
The filter doors judge a currency formula as currency.
Activity
objectstack-fleet commentedon Oct 10, 2026
Claim: PM loop round 2 · 2026-10-10T20:11Z
Session:
session_016njDy8ozy9B9Ns5Y8kAWEKAccount:
marchtian(the seat's linked user asGET /useranswers it; always the card's assignee)Branch:
claude/issue-22727-formula-currency-resultWorktree:
objectstack-issue-22727Domain:
domain:specSeat:
domain:spec#2File surface (at
origin/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.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).@objectstack/formulainferExpressionType, if that is the stamp's source). If it lives only in objectui, say so; this card does not reach into objectui..changeset/22727-*.md:@objectstack/spec(plus@objectstack/formulaif touched)minor,Clause-②: yes (widening).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 touches
field.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
wip(spec): a formula field declares a currency result (#22727)
objectstack-fleet commentedon Oct 10, 2026
os-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): spec
pnpm --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 commentedon Oct 10, 2026
os-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 commentedon Oct 10, 2026
os-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's
field :: 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 only
packages/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 with
git 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 commentedon Oct 10, 2026
✅ 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.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: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:
field.form.tsshows Currency Config for a currency formula.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.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:
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.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):
validate_expressionanswersinferredTypefrominferExpressionType, nevercurrency. The twodescribeObjectprojections omitcurrencyConfig. So an AI author keeps stampingnumberfor a money formula, which the platform honours: the status quo.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 commentedon Oct 10, 2026
Parked: 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:
ed1de8c2db),registry.tsis generated and not in git, so the base archive lacks it.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.1e9d5ba927is green on all 35 runs, and its contract review is PASS6103160720.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
objectstack-fleet commentedon Oct 11, 2026
Unlocked: #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.Unlock-action:line:1e9d5ba927, so the ACCEPT and the contract review PASS6103160720still name it;mergeable_stateisclean, and a merge-tree againstorigin/main149294c02cis clean.Blocked-by:andUnlock-action:lines are removed. No new blocker was found.Generated by Claude Code
objectstack-fleet commentedon Oct 11, 2026
Landed: 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.e86530088a(2026-10-11T03:34Z). It has one parent,7aa8d51c0f, and is an ancestor oforigin/main.1e9d5ba927.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.6103182825; at-tier contract review PASS6103160720(the FAIL6102756758on40693a3456was superseded by the lint-pin fix).FieldSchema.returnTypeadmits'currency'. A currency formula carries its currency incurrencyConfig; absent meansdynamic.@objectstack/formulaexportsinferFormulaReturn.currency.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.6103175421.This act removes
pm:dispatched;Fixes #22727closed the card.Generated by Claude Code