Skip to content

service-analytics: a dataset's currency guard compares a count that names an amount field, refusing an amount-per-row ratio as cross-currency (multi-currency, epic #8345) #28194

Closed

Description

@objectstack-fleet

epic:#8345#1 · os-bill · session_01Utp6CbTZrFqRYNgjSaSbN5

Filed by the epic PM from the contract review of PR #28181 (should-fix, routed to the code it cites). Part of #8345. ⛔ Not a claim.

维护者速读

  • 改了什么: 数据集编译期的币种一致性检查,把「对金额字段做 count」误当成金额,于是像「美元合计 ÷ 有金额的发票数」这样的比率被当成跨币种运算拒绝。修法:解析器只对 sum/avg/min/max 这类金额聚合认币种。
  • 为什么改: count 数的是行,不是钱;ADR-0144 开放项 12 的裁决原文就是「count 操作数不参与比较」,现在代码只在 count 不带 field 时才做到。
  • 风险与回滚: 只放宽一个误拒,不会让错的数字通过;回滚即撤 PR。
  • 先看: packages/services/service-analytics/src/measure-currency-coherence.ts 的 measureCurrencyResolver.resolvedOf 非派生分支。
  • 两分钟验证: 数据集里 ratio(usd_total, priced_invoices)(后者为 { aggregate: 'count', field: 'amount' })编译通过;两个不同 fixed 币种的金额求和仍被拒。
  • 需要您做的: 无。

Defect

measureCurrencyResolver.resolvedOf (measure-currency-coherence.ts, the non-derived branch) resolves a measure's currency from fieldOf(m) without reading m.aggregate. DatasetMeasureSchema.field is allowed on a count (a count of non-null values; the guard's own test dataset declares priced_invoices: { aggregate: 'count', field: 'amount' }). Such a count therefore resolves to the amount's currency (record, or a fixed code), and guard 2 compares it with the other operand. A ratio of usd_total (fixed USD) over priced_invoices (a count over a record-mode or a differently-coded amount) is refused with DATASET_INVALID "amounts in different currencies", although one operand counts rows.

The other two readers already gate on AMOUNT_AGGREGATES and get the same count right: enrichResultColumns and the analytics cube door. Ruling 6486427200 on #8345 and ADR-0144 Open item 12's note both say "a count operand is not compared".

Responsibility: PR #27991 (#27579), which wrote the resolver.

Task

  • In resolvedOf's non-derived branch, resolve a currency only for an amount aggregate (the same AMOUNT_AGGREGATES set the cube door and enrichResultColumns use); a count with a field resolves to none.
  • Pin it: a ratio and a sum derived measure over an amount and a count with field compile; the existing refusals (two different fixed codes, a record amount against a fixed one) stay red. Ablate the new test.

Lane: server (@objectstack/service-analytics) · Size: S · Review: default. Declarations, for the card to confirm: Clause-②: yes (widening) (a refusal shrinks) · Metadata-protocol: none; patch changeset.

Epic: #8345. Dedupe: PR #28181 review record; no open card carries this.

Activity

  1. added
    bugSomething isn't working
    area:recordsBusiness objects, records, the views that show data, usable forms, search
    domain:serverDomain lane — seat card indexed by label:pm:seat
    on Oct 30, 2026
  2. objectstack-fleet commented on Oct 31, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#3 · os-tesla · session_01L9qibz8HEj53Wx2uphTo2o

    Claim: PM loop round 4 · cross-lane · 2026-10-31T05:44Z
    Session: session_01L9qibz8HEj53Wx2uphTo2o (seat; implementation by card thread session session_01L9qibyimd4GfDwsijRiftT, mode:thread)
    Account: os-tesla (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/project-thread-5zv5e6 (the card thread's designated push branch, the recorded platform exception to claude/issue-<n>-<slug>)
    Worktree: objectstack-issue-28194
    Domain: domain:server (the card keeps its lane; dispatched by ui seat 3)
    Seat: domain:ui#3
    File surface: packages/services/service-analytics/src/measure-currency-coherence.ts (gate the non-derived branch on AMOUNT_AGGREGATES imported from cube-record-currency-door.ts), packages/services/service-analytics/src/analytics-service.ts (enrichResultColumns non-record arm, same gate; seat ruling), their tests, one root changeset (stop on breach; explain in the report)
    Container & model: S, mode:thread, model: default (dispatch-gates --tier re-run on the actual diff), effort: high
    Clause-②: yes (widening: two refusals shrink; card's own declaration, re-judged on the diff)
    Metadata-protocol: none
    Responsibility: PR #27991 (#27579), which wrote the resolver
    Thread-read: none
    Premise: re-measured by the card thread on main 69c39b16 before this claim (premise holds)
    Serial constraints cleared: server seat 2 answered "No objection: take both" on #28284 (6539522584) and holds no overlapping work; server seat 1 was notified on #6367 and raised no objection within the 20-minute window. ui seat 3 siblings share no file with this card.

    Maintainer 2026-10-31 05:10Z 「座位之间可以协调,你有空你就可以派」. Notice: #28284 6539514907 (+ amendment 6539527220). Order: /mnt/project-files/ui-seat-3/r2026-10-31/dispatch-28194.md.

  3. objectstack-fleet commented on Oct 31, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#3 · os-tesla · session_01L9qibyimd4GfDwsijRiftT
    os-dev-report

    {
     "issue": 28194,
     "status": "done",
     "branch": "claude/project-thread-5zv5e6",
     "pr": "https://github.com/objectstack-ai/objectstack/pull/28360",
     "session": "session_01L9qibyimd4GfDwsijRiftT",
     "premise_still_valid": true,
     "summary": "measureCurrencyResolver.resolvedOf's non-derived branch now resolves a currency only for an amount aggregate, through the existing AMOUNT_AGGREGATES export of cube-record-currency-door.ts (no copy, nothing moved), so a count over an amount field or with its own currency is never a guard-2 operand. Per the seat's ruling, enrichResultColumns' fixed/tenant arm takes the same gate, so a count over a fixed or tenant-currency amount carries no currency on its column. Premise measured on main 69c39b16 before the fix: ratio(usd_total, priced_invoices) refused DATASET_INVALID; a count with a currency key reached monetaryOperands through the same branch (closed by the same gate, pinned).",
     "tests": "head 85afc7ba. record-currency-analytics.test.ts: 8 new tests; on the unfixed source 6 failed | 54 passed (60) (expected 'EUR' to be undefined; expected the refusal message to be undefined); with the fix 60 passed (60). Ablation 1 via scripts/ablation-replace.mjs on measure-currency-coherence.ts (resolver gate forced true, anchor 1 -> 0, blob 985caf62 -> e563f99c): 3 failed | 57 passed, the three accepted-derived tests; restored blob == HEAD, git diff HEAD empty. Ablation 2 on analytics-service.ts (column gate removed, blob 0ea0b0f3 -> 624524bf): 3 failed | 57 passed, the three count-column tests; restored, git diff HEAD empty. Tests import src relatively, no dist build involved. pnpm --filter @objectstack/service-analytics test: Test Files 200 passed (200), Tests 4814 passed | 282 skipped; typecheck tsc --noEmit exit 0 (os-verify-lock VERDICT command-exit 0).",
     "gates": "dispatch-gates --commands at 85afc7ba (4 paths vs merge base 69c39b16): 62 derived; 61 exit 0; pnpm check:dual-build-cjs-loads exit 3 PREREQUISITE NOT MET (needs every package dist; only the service-analytics closure was built) = NOT MEASURED, declared narrowing, CI Lint and Repo Gates runs it. check-plugin-teardown-shape --self-test first exit 3 on the shallow clone (pinned fixture commit absent), exit 0 after fetching that commit. --ran reconciliation: 62 derived accounted for, 61 run, 1 NOT-MEASURED, 0 UNRUN. PR check-runs: in_progress at report time.",
     "line_budget": "114 changed lines (+107 / -7), under the 3000 human-merge threshold",
     "files_changed": [
      ".changeset/28194-count-is-not-an-amount.md",
      "packages/services/service-analytics/src/measure-currency-coherence.ts",
      "packages/services/service-analytics/src/analytics-service.ts",
      "packages/services/service-analytics/src/__tests__/record-currency-analytics.test.ts"
     ],
     "deviations": [
      "analytics-service.ts (enrichResultColumns) is outside the card's own task list; folded in by the seat's ruling and named in the claim's file surface",
      "main checkout detached to host the worktree on the platform-designated branch (worktree path objectstack-28194, not objectstack-issue-28194)",
      "one shallow-clone fetch of a pinned self-test fixture commit"
     ],
     "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
     "mcp_calls": "4 — mcp__github__issue_read (get x1, get_comments x2) and ToolSearch load; 0 writes",
     "api_writes": "2 — relay pr_create (objectstack-ai/objectstack#28360), relay comment os-dev-report on #28194; plus 1 git push",
     "open_questions": [],
     "out_of_scope_findings": [
      "carrier: none · noted, not filed: an author-declared currency on a count still labels that count's column (unchanged behaviour, not judged by the card or the ruling)"
     ]
    }
  4. objectstack-fleet commented on Oct 31, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#3 · os-tesla · session_01L9qibz8HEj53Wx2uphTo2o

    Landed · 2026-10-31T07:08Z

    probe reading
    PR #28360 merged through the queue at 2026-10-31T07:07:55Z, merge commit d35db2cca7 (ACCEPT 6540250940)
    this card closed completed by the PR's Fixes line

    State: pm:dispatched removed (closed card).

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

Metadata

Metadata

Assignees

Labels

area:recordsBusiness objects, records, the views that show data, usable forms, searchbugSomething isn't workingdomain:serverDomain lane — seat card indexed by label:pm:seatpriority:p3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions