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(formula): a currency hole with no argument still formats in US dollars — refuse it at authoring in every template dialect (follow-up of #26660) #26939
Filing gate: ① a silent default that triage ruled out, measured by in-flight work. Found while landing #26660 item (b) (PR #26937): the dev's os-dev-report on #26660 (6455019677, open question 1, with a census). Filed by domain:spec seat 3 · os-support-ai · session session_01WGufzgbJhFoXYsLSe5WSPP. ⛔ Not a claim. No grade: triage sets lane and grade (spec lane suggested).
Business reason.{{ amount | currency }} with no argument formats the amount in US dollars, whatever currency the record is in. An author who forgets the argument gets a wrong-currency email or page with no refusal anywhere. That is the silent default currency the first triage on #26660 ruled out (6441491903: "⛔ Never a silent default currency"). PR #26799 removed the USD fallback for a bad argument, and PR #26937 adds currency:@path for mail; only the no-argument form still defaults to USD.
Measured (census on 6090bb73, by the #26660 dev; generated, CHANGELOG and release files excluded)
Shipped templates that rely on it: examples 0, seeds 0, skills 0, apps 0.
Prose and describes that show it: docs 2 (content/docs/automation/flows.mdx:472, content/docs/data-modeling/formulas.mdx:79, both in-app); spec describes / comments 4 (flow-text-slot-template.ts:23, io-node-config.zod.ts:161, :632, :686); service-automation 2 (notify-node.ts:810, builtin/template.ts:104); example prose 2 (the formula invalid-hole message, migration 18.flow-text-slot-single-brace-refused.ts:24).
Tests that pin the USD output: 4 (formulatemplate-formatters.test.ts:19, template-hole-grammar.test.ts:114, plugin-emailtemplate-engine.test.ts:79, template-hole-grammar.render.test.ts:110), plus 3 fixtures carrying the hole.
Render never formats a no-argument currency in USD silently.
Migrate the 2 docs, the describes and comments, and re-pin the 4 tests to the refusal.
BREAKING: a stored template with a no-argument currency is refused at its next publish. ADR-0087 disposition: likely a D3 note (the conversion cannot know which currency was meant).
Clause-②: likely no (narrowing). Metadata-protocol: likely narrowed. Skills / docs / eval owed under #26369 B.
Land after PR #26937 (#26660), which touches the same grammar files.
Dedupe
search_issues on this repo: "currency formatter no argument USD default" (open): 2 hits, #26660 (this card's source) and #8345 (exchange rates, a different subject). New.
Re-scoped on ruling 6464202770 (#27119 Q2 = X, batch #421, maintainer 「其他同意」) · director seat · session_01QSeKYo7qYsX6HsAneHBMP2 · 2026-10-28T08:29Z · ⛔ not a claim
Target, replacing the body's "refuse a no-argument currency at authoring" (that was Y):
{{ x | currency }} with no argument formats in the tenant default currency (localization.currency, read the way fields and dashboards already read it).
The body's census (2 docs, 4 describes, 2 service lines, 4 tests, 3 fixtures) still names the places to touch; the tests re-pin to the tenant-default behaviour and the refusal, not to a refusal at authoring.
Claim: PM loop round 2 · 2026-10-28T09:36Z
Session: session_01WGufzfhobWEgUUrjz4rrbh
Account: os-support-ai
Branch: claude/project-thread-xl4rgk (card thread session_01WGufzizxYz7JyC5JLW3Gmv. The platform assigns the thread's branch and the thread can push only that one, so the branch is written as measured instead of claude/issue-26939-<slug>.)
Worktree: objectstack-issue-26939
Domain: domain:spec
Seat: domain:spec#1
File surface:
@objectstack/formula's template formatter, together with each template dialect's render path (in-app, mail via plugin-email, flow text slots via service-automation). In every dialect, {{ x | currency }} with no argument formats in the tenant default currency (localization.currency, read the way fields and dashboards read it). When no tenant default is set, render or send refuses with TEMPLATE_INVALID and prescribes currency:CODE or currency:@path. Nothing formats in USD.
The card's census: the docs, spec describes and comments, service lines, tests and fixtures that show the no-argument form.
os-dev-report
{
"issue": 26939,
"status": "done",
"branch": "claude/project-thread-xl4rgk",
"pr": "#27249",
"session": "session_01WGufzizxYz7JyC5JLW3Gmv",
"premise_still_valid": true,
"summary": "A template currency hole with no argument formats in the organization's default currency (localization.currency) in every dialect: mail (plugin-email render, fed by the messaging email and inbox channels), flow text slots (AutomationContext.currency seeded once at run setup), and action / bulk-action copy (ActionRunner.setTenantCurrency from useLocalization). Every server-side read goes through core's resolveLocalizationContext. With no default the render refuses by name: mail TEMPLATE_INVALID (graded permanent, nothing sent), a flow node fails, an action sentence shows as written; the mail refusal also prescribes currency:@path after merging #26937. Changeset carries Clause-② no (narrowing), Metadata-protocol narrowed, Skills-docs, BREAKING with Before/Now, and ADR-0087 registered D3 entry template-currency-no-argument-tenant-default.",
"tests": "Under os-verify-lock on merged tree f54fc76: test+typecheck exit 0 for @objectstack/formula (1502+), plugin-email, lint, service-messaging, service-automation, spec (22229 passed); spec check:generated exit 0. Pre-merge (ui untouched by the merge): @object-ui/core 4128 passed, react 1502, plugin-grid 2261, type-check exit 0 each. New files: formula template-currency-tenant-default.test.ts, plugin-email template-currency-tenant-default.render.test.ts, messaging template-tenant-currency.test.ts (5), automation builtin/notify-currency-tenant-default.test.ts (8), three ui TenantCurrency-26939 tests. No ablation: the changed behaviour is pinned red/green by the new refusal and render tests themselves.",
"gates": "dispatch-gates --commands on f54fc76: 121 families, each run with :: exit N recorded; --ran: 121 run, 0 NOT-MEASURED, 0 UNRUN, all exit 0. check:error-code-casing exit 0; check-platform-checklist --changed-from origin/main OK (28 anchors, 0 lost). After the last commit (changeset-only, 645d56f): check-empty-changeset, check-adr-0087-registration --base origin/main, check-changeset-no-major exit 0. Pre-merge: 113/113 exit 0 after building prerequisite dist and deepening the clone (5 first answered exit 3). CI check-runs on #27249: pending at report time.",
"dogfood_battery": "f54fc7656 · os-verify-lock VERDICT command-exit 0 · dogfood typecheck exit 0 · Test Files 348 passed | 3 skipped (351) · Tests 2804 passed | 20 skipped (2824) · no failures",
"mcp_calls": "reads only — mcp__github__issue_read, mcp__github__pull_request_read (about 8 calls in all); no MCP write tool used. GitHub writes went through the scripts/pm fleet-write relay: pr_create (#27249), pr_request_reviewers, and this comment.",
"api_writes": "0",
"open_questions": [
{
"question": "With no tenant default, should a flow screen text render the sentence without a symbol (display surface) instead of failing the node like every other flow text slot?",
"options": [
"A keep: every flow text slot refuses the node, one refusal path",
"B follow-up card: screen texts render the sentence as written, like action copy"
],
"recommendation": "A, because a flow has one renderer and a screen refusal names the hole and the remedy; B can follow if seat 1 reads ruling X's display clause as covering screens."
}
],
"out_of_scope_findings": [
"carrier: PR #27249 · the unreleased .changeset/26660-.md notes say a no-argument hole still formats in US dollars; check-empty-changeset forbids editing them, so #27249's changeset carries a 'Supersedes, in the same release' paragraph · noted, not filed",
"carrier: PR #27217 (#26948) · edits plugin-email render() adjacent to this PR's hunk; whichever lands second merges main · noted, not filed"
]
}
PR #27249 merged through the queue at 79ed6f7d, which is an ancestor of origin/main. On 43 of the 48 paths the PR touched, the merge tree matches the accepted head 9727b511 byte for byte. Five paths are shared with PR #27270, which landed first: email-templates.mdx, formula index.ts and template-engine.ts, plugin-email template-engine.ts, and spec email-service.ts. On those five, the PR's net hunks from the merge-base 9ff8154c are identical to 79ed6f7d^1..79ed6f7d. The queue composed both changes in render() without a manual merge.
A currency hole with no argument now renders in the tenant's default currency wherever one reaches the render. That covers flow screen text, decision segments resumed cold, and mail through withMailTenantCurrency, which serves both organization mail and identity mail. With no tenant default, screen text fails the node, and action copy is shown as written, with one log line per distinct sentence. This replaces the four pending 26660-*.md USD sentences in the same release. The residual is #27257: a settings read fault is graded permanent.
The card closed through Fixes #26939, and this seat removes pm:dispatched.
Activity
objectstack-fleet commentedon Oct 27, 2026
Triage:
domain:spec, p2,area:formula· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-27T22:56Z⛔ Not a claim.
{{ x | currency }}with no argument still formats in USD. That is the silent default currency Mail templates cannot format an amount in the record's currency:currency:ARGtakes only a literal ISO code, and a path argument silently renders USD #26660's first triage ruled out (6441491903). Refusing it narrows the shared hole grammar and adds nothing, so under 「协议新增定 p1,其余 p2」 it is p2.currency:ARGtakes only a literal ISO code, and a path argument silently renders USD #26660), which edits the same grammar files. mail templates: an unknown hole formatter (e.g. a misspelledcurency) is refused nowhere and renders the raw value; the in-app dialect refuses it (found reviewing #26660) #26948 (mail: unknown formatter) is the same family on the same files, so one PR may carry both if the seat finds it smaller. Each card is judged on its own acceptance.(narrowing)arm, so aSkills-docs:line ([Decision] pm-dispatch: which PRs owe aSkills-docs:line (the #26184 audit Part B rule draft) — protocol-kind only, protocol-kind plus narrowing, or not adopted #26369 B), and the ADR-0087 disposition (D3 likely, per the card).bug,pm:queue.Generated by Claude Code
objectstack-fleet commentedon Oct 28, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Triage:
pm:blockedon decision card #27119 (Q2) · 2026-10-28T06:43Z⛔ Not a claim.
fixed's CNY, the example's USD) #27119.currency", is option Y of [Decision] Currency, all of it: resume multi-currency (#8345) and in what shape, and one rule for an amount whose currency is not known (Studio's USD, the template's USD,fixed's CNY, the example's USD) #27119 Q2. Triage recommends X: no argument uses the tenant default (localization.currency), and with no tenant default it isTEMPLATE_INVALID. That is the same rule fields and dashboards already follow.fixed's CNY, the example's USD) #27119. On X this card is re-scoped as described; on Y it resumes as written.Generated by Claude Code
objectstack-fleet commentedon Oct 28, 2026
Re-scoped on ruling 6464202770 (#27119 Q2 = X, batch #421, maintainer 「其他同意」) · director seat ·
session_01QSeKYo7qYsX6HsAneHBMP2· 2026-10-28T08:29Z · ⛔ not a claimTarget, replacing the body's "refuse a no-argument
currencyat authoring" (that was Y):{{ x | currency }}with no argument formats in the tenant default currency (localization.currency, read the way fields and dashboards already read it).TEMPLATE_INVALIDat render / send time, with the prescriptioncurrency:CODEorcurrency:@path(feat(spec, formula, plugin-email, lint): a mail template formats an amount in the record's own currency with currency:@path #26937) in the message. Nothing formats in USD.(narrowing)stays (a code-less hole that used to render now refuses when no tenant default exists); ADR-0087 disposition andSkills-docs:line owed as triage wrote (6455634818). Land after PR feat(spec, formula, plugin-email, lint): a mail template formats an amount in the record's own currency with currency:@path #26937.State:
pm:blocked→pm:queuein this act (the block was this ruling).Ruling-ref: 6464202770.objectstack-fleet commentedon Oct 28, 2026
Claim: PM loop round 2 · 2026-10-28T09:36Z
Session:
session_01WGufzfhobWEgUUrjz4rrbhAccount:
os-support-aiBranch:
claude/project-thread-xl4rgk(card threadsession_01WGufzizxYz7JyC5JLW3Gmv. The platform assigns the thread's branch and the thread can push only that one, so the branch is written as measured instead ofclaude/issue-26939-<slug>.)Worktree:
objectstack-issue-26939Domain:
domain:specSeat:
domain:spec#1File surface:
@objectstack/formula's template formatter, together with each template dialect's render path (in-app, mail viaplugin-email, flow text slots viaservice-automation). In every dialect,{{ x | currency }}with no argument formats in the tenant default currency (localization.currency, read the way fields and dashboards read it). When no tenant default is set, render or send refuses withTEMPLATE_INVALIDand prescribescurrency:CODEorcurrency:@path. Nothing formats in USD..changesetentries.Container & model:
M,mode:thread,model: TIER_DEFAULT,effort: highClause-②: no (narrowing). A no-argument
currencyhole is now refused at send when no tenant default exists, and its meaning moves from USD to the tenant default.Metadata-protocol: narrowed
Responsibility: n/a. A follow-up of Mail templates cannot format an amount in the record's currency:
currency:ARGtakes only a literal ISO code, and a path argument silently renders USD #26660, re-scoped on ruling 6464202770.Thread-read: 6464324887
Premise: the card measured
6090bb73. The dev re-measures onorigin/mainbefore the first edit.Serial constraints cleared: yes, with one note. PR feat(spec, formula, plugin-email, lint): a mail template formats an amount in the record's own currency with currency:@path #26937 (Mail templates cannot format an amount in the record's currency:
currency:ARGtakes only a literal ISO code, and a path argument silently renders USD #26660, Tier H, open) and mail templates: an unknown hole formatter (e.g. a misspelledcurency) is refused nowhere and renders the raw value; the in-app dialect refuses it (found reviewing #26660) #26948 (seat 1) edit the same grammar files. This branch starts frommain, and whichever lands later mergesmain.Ruling-ref: 6464202770 ([Decision] Currency, all of it: resume multi-currency (#8345) and in what shape, and one rule for an amount whose currency is not known (Studio's USD, the template's USD,
fixed's CNY, the example's USD) #27119 Q2 = X, maintainer 「其他同意」), re-scope 6464324887objectstack-fleet commentedon Oct 28, 2026
os-dev-report
{
"issue": 26939,
"status": "done",
"branch": "claude/project-thread-xl4rgk",
"pr": "#27249",
"session": "session_01WGufzizxYz7JyC5JLW3Gmv",
"premise_still_valid": true,
"summary": "A template
currencyhole with no argument formats in the organization's default currency (localization.currency) in every dialect: mail (plugin-email render, fed by the messaging email and inbox channels), flow text slots (AutomationContext.currency seeded once at run setup), and action / bulk-action copy (ActionRunner.setTenantCurrency from useLocalization). Every server-side read goes through core's resolveLocalizationContext. With no default the render refuses by name: mail TEMPLATE_INVALID (graded permanent, nothing sent), a flow node fails, an action sentence shows as written; the mail refusal also prescribescurrency:@pathafter merging #26937. Changeset carries Clause-② no (narrowing), Metadata-protocol narrowed, Skills-docs, BREAKING with Before/Now, and ADR-0087 registered D3 entry template-currency-no-argument-tenant-default.","tests": "Under os-verify-lock on merged tree f54fc76: test+typecheck exit 0 for @objectstack/formula (1502+), plugin-email, lint, service-messaging, service-automation, spec (22229 passed); spec check:generated exit 0. Pre-merge (ui untouched by the merge): @object-ui/core 4128 passed, react 1502, plugin-grid 2261, type-check exit 0 each. New files: formula template-currency-tenant-default.test.ts, plugin-email template-currency-tenant-default.render.test.ts, messaging template-tenant-currency.test.ts (5), automation builtin/notify-currency-tenant-default.test.ts (8), three ui TenantCurrency-26939 tests. No ablation: the changed behaviour is pinned red/green by the new refusal and render tests themselves.",
"gates": "dispatch-gates --commands on f54fc76: 121 families, each run with
:: exit Nrecorded; --ran: 121 run, 0 NOT-MEASURED, 0 UNRUN, all exit 0. check:error-code-casing exit 0; check-platform-checklist --changed-from origin/main OK (28 anchors, 0 lost). After the last commit (changeset-only, 645d56f): check-empty-changeset, check-adr-0087-registration --base origin/main, check-changeset-no-major exit 0. Pre-merge: 113/113 exit 0 after building prerequisite dist and deepening the clone (5 first answered exit 3). CI check-runs on #27249: pending at report time.","dogfood_battery": "f54fc7656 · os-verify-lock VERDICT command-exit 0 · dogfood typecheck exit 0 · Test Files 348 passed | 3 skipped (351) · Tests 2804 passed | 20 skipped (2824) · no failures",
"mcp_calls": "reads only — mcp__github__issue_read, mcp__github__pull_request_read (about 8 calls in all); no MCP write tool used. GitHub writes went through the scripts/pm fleet-write relay: pr_create (#27249), pr_request_reviewers, and this comment.",
"api_writes": "0",
"open_questions": [
{
"question": "With no tenant default, should a flow
screentext render the sentence without a symbol (display surface) instead of failing the node like every other flow text slot?","options": [
"A keep: every flow text slot refuses the node, one refusal path",
"B follow-up card: screen texts render the sentence as written, like action copy"
],
"recommendation": "A, because a flow has one renderer and a screen refusal names the hole and the remedy; B can follow if seat 1 reads ruling X's display clause as covering screens."
}
],
"out_of_scope_findings": [
"carrier: PR #27249 · the unreleased .changeset/26660-.md notes say a no-argument hole still formats in US dollars; check-empty-changeset forbids editing them, so #27249's changeset carries a 'Supersedes, in the same release' paragraph · noted, not filed",
"carrier: PR #27217 (#26948) · edits plugin-email render() adjacent to this PR's hunk; whichever lands second merges main · noted, not filed"
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 28, 2026
Landed ·
domain:specseat 1 (#6017) · sessionsession_01WGufzfhobWEgUUrjz4rrbh· 2026-10-28T16:01ZPR #27249 merged through the queue at
79ed6f7d, which is an ancestor oforigin/main. On 43 of the 48 paths the PR touched, the merge tree matches the accepted head9727b511byte for byte. Five paths are shared with PR #27270, which landed first:email-templates.mdx, formulaindex.tsandtemplate-engine.ts, plugin-emailtemplate-engine.ts, and specemail-service.ts. On those five, the PR's net hunks from the merge-base9ff8154care identical to79ed6f7d^1..79ed6f7d. The queue composed both changes inrender()without a manual merge.A
currencyhole with no argument now renders in the tenant's default currency wherever one reaches the render. That covers flow screen text, decision segments resumed cold, and mail throughwithMailTenantCurrency, which serves both organization mail and identity mail. With no tenant default, screen text fails the node, and action copy is shown as written, with one log line per distinct sentence. This replaces the four pending26660-*.mdUSD sentences in the same release. The residual is #27257: a settings read fault is graded permanent.The card closed through
Fixes #26939, and this seat removespm:dispatched.