Skip to content

fix(service-analytics): a consolidated measure over the home field works unchanged (proof), and the record-currency refusal names the home field (multi-currency M3-1) - #30176

Merged
objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/project-thread-lsginq
Nov 4, 2026
Merged

objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/project-thread-lsginq

Conversation

@os-bill

@os-bill os-bill commented Nov 4, 2026 •

Copy link
Copy Markdown
Collaborator

Requested by Bill · project thread

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

Fixes #27195
Ruling-ref: 6465431966

Clause-②: yes (widening) — AnalyticsServiceConfig.sourceFieldMeta (an exported config type) may answer one more optional key, homeAmountField, which AnalyticsServicePlugin relays; nothing an author writes changes
Metadata-protocol: none

维护者速读

  • 改了什么: 新增证明测试:三种记录币种(USD/EUR/CNY)的行经写入投影存入影子列后,数据集度量 sum 影子列在内存预览、SQLite、Postgres 上都直接得出本位币合计,代码路径零改动;未分组汇总原始金额的拒绝文案现在同时给出两个药方:指向影子列(宿主透传时点名),或按币种码分组。
  • 为什么改: 仪表盘是客户看合并数的地方;原文案只教「按币种分组」,没告诉作者要一个合计该指向影子列。
  • 风险与回滚: 小。错误码、状态、member/field/object 信封不变,只改文案;sourceFieldMeta 多一个可选键(Clause-② widening),宿主不答时文案不点名字段。回滚即撤 PR。
  • 先看: packages/services/service-analytics/src/cube-record-currency-door.ts 的文案,再看 home-amount-consolidated-measure.test.ts。
  • 两分钟验证: OS_TEST_POSTGRES_URL=… npx vitest run src/__tests__/home-amount-consolidated-measure.test.ts(36 条);在 main 上跑同一文件,证明部分全绿、文案断言红。
  • 需要您做的: 无。

What changes

The proof (no product code). src/__tests__/home-amount-consolidated-measure.test.ts writes four invoices in three record currencies (USD, EUR and the home CNY) through the engine, whose write-time projection (ADR-0144 D5, home-amount-projection.ts) stores each amount converted into the home currency in the declared homeAmount.field. A dataset measure sum over that home field answers the home-currency total through queryDataset (dataset compile + query), on:

  • SQLite and PostgreSQL, each on both strategies (NativeSQLStrategy; ObjectQLStrategy, narrowed to the engine aggregate);
  • the draft-preview in-memory evaluator, over the very rows the projection stored (read back from the store). This "memory" cell is that evaluator, not driver-memory.

Pinned per cell: the projection fixture (home amounts and rates as stored); the ungrouped KPI (2010 CNY); per-currency subtotals grouped by the code field and a totals grand total equal to the KPI; the column labelled with the home currency and no per-row currencyField (the home field is dynamic); the inferred cube's amount_home_sum; a relationship path (a line cube joined to the invoice: invoice.amount refused naming invoice.amount_home and invoice.currency_code, invoice.amount_home served on the native strategy); INVALID_FIELD and status 400 on every refusal path (dataset, inferred cube, joined cube, draft preview). Control: the raw amount grouped by code is served per currency and adds to a different number (270). On main the proof assertions already pass, so the consolidated path is the unchanged code path.

The door message (text only). assertRecordCurrencyAggregatesGrouped (#27187, PR #27338) refused an ungrouped sum / avg / min / max over a record-mode amount with one prescription: group by the code field. It now names both:

  • for one total in the home currency, point the measure at the amount's home field, by name (homeAmount.field) when the host relays it, or else as "the field its currencyConfig.homeAmount.field names", with the declaration to add if it has none (true whether or not the object declares one);
  • for one row per currency, add the dimension over the code field.

Code INVALID_FIELD, status 400, member / param / cube / field / object are unchanged; no new refusal code. The home field reaches the door through sourceFieldMeta, which now answers homeAmountField beside currencyField (record mode only), relayed by AnalyticsServicePlugin from currencyConfig.homeAmount.field.

Not in this diff. The engine's own aggregate() door (packages/objectql/src/record-currency-aggregate-door.ts) already names a stored home-currency amount field generically; it is outside this card's file surface. ⛔ No ui/** / plugin-dashboard change (ui lane off). #27193 untouched.

Why

ADR-0144 D6: a consolidated total aggregates the stored home field. The proof shows that needs nothing new; the message makes the refusal prescribe it.

Verification

  • home-amount-consolidated-measure.test.ts: 36/36 at the current head (30/30 at e430075a) with OS_TEST_POSTGRES_URL set (PostgreSQL 16, local). Ablation at e430075a: the same file on main's three source files: 14 failed, every one a message assertion; every proof assertion passed.
  • pnpm --filter @objectstack/service-analytics typecheck: exit 0. eslint on the changed files: exit 0.
  • Package suite with OS_TEST_POSTGRES_URL (at e430075a): 5267 passed, 1 failed: read-scope-temporal-coercion.test.ts "the server is not on UTC (the premise of the PostgreSQL cells)", a premise check on the local server's zone (local PostgreSQL runs on Etc/UTC; CI provisions a skewed zone). Not touched by this diff.
  • dispatch-gates --commands (at e430075a): 62 commands, exit codes captured before any pipe: 61 exit 0; pnpm check:dual-build-cjs-loads exit 3, NOT MEASURED (it reads every package's dist/, and this worktree built only this package's closure), left to CI. check-plugin-teardown-shape --self-test first exited 3 on the shallow clone and passed (exit 0) after fetching its pinned fixture commit.

One changeset, @objectstack/service-analytics patch.

Human Minimum

  • 1. Schemas start as Zod; packages/spec holds no runtime logic. (spec not touched)
  • 2. Every name I added uses the casing for its kind.
  • 3. No formatter ran; eslint passes on every changed file (whole-repo pnpm lint left to CI).
  • 4. One issue, one branch off an up-to-date main, one pull request.
  • 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset.
  • 6. packages/spec touched: n/a.
  • 7. No hand edits to content/docs/references/, packages/*/CHANGELOG.md or content/docs/releases/.
  • 8. Title, body and comments are in English.
  • The Before Submitting list in CONTRIBUTING.md is done.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Utp6CbmDUh2Sx7fvaGgu7r

@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface labels Nov 4, 2026
@github-actions

github-actions Bot commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/service-analytics, touching 5 documentable anchor(s).

1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/plugins/packages.mdx (via AnalyticsServicePlugin (symbol, a top-level class))

⛔ 2 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v17/17-6.mdx (via AnalyticsServicePlugin (symbol, a top-level class), sourceFieldMeta (symbol, a field of interface AnalyticsServiceConfig))
  • content/docs/releases/v17/17-7.mdx (via sourceFieldMeta (symbol, a field of interface AnalyticsServiceConfig))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • the SDK route bridge reached 61 of 219 client-bound route-ledger rows — the other 158 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 158: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 103 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 10 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 59e64635f4a6bf03ecbe17afb63b8af5908eb2f9 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 53e861a2fe4d1b217a45e471d089c9ede1c7f55a — the merge of head d4e33ba109630f0fff336b35892cf7af71e108ed into base 59e64635f4a6bf03ecbe17afb63b8af5908eb2f9, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 53e861a2fe4d1b217a45e471d089c9ede1c7f55a && git checkout 53e861a2fe4d1b217a45e471d089c9ede1c7f55a
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 59e64635f4a6bf03ecbe17afb63b8af5908eb2f9 d4e33ba109630f0fff336b35892cf7af71e108ed && git checkout -B drift-repro 59e64635f4a6bf03ecbe17afb63b8af5908eb2f9 && git merge --no-ff d4e33ba109630f0fff336b35892cf7af71e108ed

node scripts/docs-audit/affected-docs.mjs --json 59e64635f4a6bf03ecbe17afb63b8af5908eb2f9

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 59e64635f4a6bf03ecbe17afb63b8af5908eb2f9 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor

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

Served-tier: TIER_DEFAULT (the seat's own review of record for the Clause-②: yes declaration; reviewer: opus subagent of the epic PM seat). Verdict at e430075: FAIL (one must-fix, REWORK). The claim's Clause-②: line is revised to yes (widening).

Seat review · PR #30176 @ e430075 · TIER_DEFAULT
Verdict: FAIL

Head-sha: e430075 (PR head.sha read via gh api; matches the reviewed tree)
Card: #27195 · claim 6640812575 · os-dev-report 6641583391 · dispatch /mnt/project-files/epic-8345/dispatch-27195.md
Diff: origin/main...e430075, 5 files (+435/-17): .changeset/27195-record-currency-door-names-home-field.md,
src/tests/home-amount-consolidated-measure.test.ts (new), src/analytics-service.ts, src/cube-record-currency-door.ts, src/plugin.ts
(all under packages/services/service-analytics).

The verdict is FAIL on one mechanical must-fix. Everything else checks out.

Must-fix

  1. packages/services/service-analytics/src/tests/home-amount-consolidated-measure.test.ts:289, :326, :338, :362
    Issue: four of the six refusal cases assert code but not status. review-checklist 〈断言与收益〉 says a missing
    code/status assertion is REWORK, and a green run does not count in its place. The cases are: :289 (grouped query
    plus totals grand total over raw_total), :326 (amount with no homeAmount), :338 (inferred cube amount_sum),
    :362 (draft-preview evaluator). Two of them run a different path from the one case that does assert status (:314):
    the inferred cube (no dataset compile) and the in-memory preview evaluator. On those paths the envelope could
    actually differ.
    Evidence: grep -n "status).toBe" finds only :314. Fix: add expect(err.status).toBe(400) (and
    (result as Refusal).status at :289) to each case.

Should-fix (non-blocking)

  1. src/cube-record-currency-door.ts:112-113. When homeField is undefined, the message says "declare
    currencyConfig.homeAmount on 'amount'". That branch also runs when a host wires sourceFieldMeta but never
    answers the new optional homeAmountField. Every host built before this PR is such a host, and so is any host
    written by hand. On those hosts the message tells the author to add a declaration the object may already have.
    The changeset says that host "gets the message without the field's name", which is true but understated. Better
    wording would cover both cases, e.g. "point the measure at its home field (currencyConfig.homeAmount.field;
    declare one if the amount has none)". The shipped AnalyticsServicePlugin relays the key, so the default
    composition is not affected.
  2. No test covers the relationship-path prefix of the home remedy (${prefix}${homeField} for a rel.amount
    measure, door :107-111). The prefix logic is the same code the code-field path already used, so the risk is low.
    A one-line pin would close it.
  3. The "memory" cell is the draft-preview in-memory evaluator, not @objectstack/driver-memory. Service-analytics
    has no separate InMemoryStrategy (src/strategies/ has native-sql and objectql only), so this is a reasonable
    reading of "memory". A driver-memory + ObjectQLStrategy cell was not pinned. Record the reading, or add the cell.
  4. CI at this head skipped "Temporal Conformance (live PG + MySQL)", which is the job the test docblock says sets
    OS_TEST_POSTGRES_URL. The PostgreSQL cell is therefore not run by CI on this PR. I ran it locally (below).

Seat actions (not dev must-fix)

  • The claim 6640812575 carries the line-initial Clause-②: no. The PR body and changeset declare
    Clause-②: yes (widening), and the diff confirms yes (see item 3 below). landing-operations.md: the enqueue gate
    reads only the claim comment's line-initial key, and a wrong no is an auditable false declaration. Revise the
    claim's Clause-② line before ready/enqueue.
  • contract-review.md: a debt that comes only from a Clause-②: yes declaration is TIER_DEFAULT, recorded by the lane
    seat. The record of this review must be posted in the matching form (## Contract review, Served-tier: TIER_DEFAULT, Head-sha: in its own code span, items ①②③, Local-runs:). Template:
    node scripts/pm/record-recognisers.mjs --template --face declaration.

Checks against the brief

  1. Proof (sum over homeAmount across three record currencies, through dataset compile + query): HOLDS.
    • Rows USD 100/20, EUR 100, CNY 50 go through engine.insert, so the real D5 projection runs. The test first pins
      the stored amount_home and amount_rate per row (800/160/1000/50). It then calls queryDataset(DATASET, ...) on
      both strategies × SQLite/PG, and on the draft-preview evaluator. Result: 2010 ungrouped, {USD 960, EUR 1000,
      CNY 50} grouped, totals grand total 2010, column currency: 'CNY' with no currencyField. The raw control
      sums to 270, which is not 2010.
    • Ablation A1, home_total pointed at amount instead of amount_home: 10 failed / 20 passed. All the KPI,
      grouped and preview proof cases go red.
    • Ablation A2, homeAmount removed from INVOICE so the projection never fills the home field: 22 failed, the
      fixture pin included.
    • Ablation A3, main's three source files with the PR test: 14 failed / 16 passed. All 14 are message assertions;
      every proof assertion passes. This confirms the dev's "passes on main unchanged" (no product code was needed for
      the proof) and the dev's 14-red count.
  2. Door message: HOLDS. The message names both remedies. The home field is named when it is declared
    (point the measure at '<prefix><homeAmount.field>' ... (homeAmount.field)). Otherwise the message names the
    declaration to add, then "or add the dimension over ''". Code, status, member, param, cube, field and
    object are unchanged: the same invalidMemberError and the same err.field/err.object assignments. No new
    code. No existing test pinned the old sentence, so nothing else moved. The objectql engine door is untouched.
  3. Clause-② yes (widening): CORRECT and purely additive.
    • AnalyticsServiceConfig is exported from the package entry (src/index.ts:5, exports["."]), so the type is
      public surface.
    • sourceFieldMeta's return type gains the optional homeAmountField?: string. A host returning the old shape
      still type-checks, and the only reader is the door. There are no out-of-package implementers; repo grep of
      sourceFieldMeta outside the package finds only docs and comments.
    • The door's own signature changed (recordCurrencyFieldOf became recordCurrencyOf, which returns
      {codeField, homeField?}). The door is not exported from index.ts, and its only caller is
      analytics-service.ts:3935. Internal.
    • The value is fed from f.currencyConfig.homeAmount.field, and only when currencyMode is 'record'
      (plugin.ts:1463-1472).
    • Changeset patch + Metadata-protocol: none is correct under AGENTS.md Post-Task step 3: kind none takes
      patch, and a non-protocol widening ships patch. The changeset sentences were checked one by one against the diff
      and are accurate.
    • Necessity: the widening was not strictly necessary. getObjectDeclaration is already reachable on the service
      (analytics-service.ts:1183/1528; plugin wires the registry's field map). The door could have read
      fields[col].currencyConfig.homeAmount.field from it and stayed Clause-②: no. That seam is the access-narrowing
      exposure gate, though: it is fail-closed by throwing and typed fields?: unknown. The code field already comes
      through sourceFieldMeta, and its docblock calls it "the one seam" for field display semantics. Keeping the code
      field and the home field on one seam is the better design. Accepted as chosen.
  4. Out of scope: CLEAN. No ui/, skills/, docs/adr/, .claude/, or packages/spec/src/** paths in the diff
    (gh pulls/30176/files lists the same 5 files). cli: os migrate recompute-home-amounts --object X [--since DATE] [--apply] — report then apply; rows with a missing rate are listed and skipped (multi-currency M2-3, epic #8345) #27193 untouched.

Checks run (fresh worktree at e430075; install with npm_config_nodedir=/opt/node22)

  • pnpm install --frozen-lockfile --filter "@objectstack/service-analytics...": exit 0. The first two attempts in the
    long scratchpad path exited 1: the tsx IPC socket path is too long (EADDRINUSE). The worktree was moved to a short
    path.
  • pnpm turbo run build --filter "@objectstack/service-analytics^...": exit 0 (14/14).
  • npx vitest run src/__tests__/home-amount-consolidated-measure.test.ts, no PG: exit 0, 15 passed / 15 skipped
    (pg cell named skip).
  • Same file, with OS_TEST_POSTGRES_URL (local PostgreSQL 16): exit 0, 30/30.
  • Ablations A1 / A2 / A3 (above): exit 1 / 1 / 1, which is the expected red. 10 / 22 / 14 failed.
  • pnpm test (whole package, PG set): exit 1, 5267 passed / 1 failed / 2 skipped. The one failure is
    read-scope-temporal-coercion "the server is not on UTC", a premise check on the local server's zone.
  • The same file rerun with the PG server timezone set to Asia/Shanghai: exit 0, 15 passed / 1 skipped. Not caused by
    this diff.
  • pnpm typecheck (service-analytics): exit 0.
  • npx eslint on the 4 changed .ts files: exit 0.
  • CI check-runs at head (gh api): 47 runs. Lint & Repo Gates success, TypeScript Type Check success, Check Changeset
    success, Governed Surface Queue Guard success, Dogfood Regression Gate success. Test Core shards 1/3/4/6 were
    in_progress at read time and the others success. Temporal Conformance skipped.

Local-runs: probe — the proof file ×3 ablations, package suite, typecheck and eslint, all on a throwaway worktree.
Nothing pushed, no GitHub writes.

@objectstack-fleet

Copy link
Copy Markdown
Contributor

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

Served-tier: TIER_DEFAULT · delta e430075..d4e33ba · PASS. The claim's Clause-②: line already reads yes (widening) (comment 6640812575, edited).

Seat review (delta) · PR #30176 · e430075..d4e33ba · TIER_DEFAULT
Verdict: PASS

Head-sha: d4e33ba (equals PR head.sha via gh api at review time)
Delta commits: 28e570c (tests: status pins, relationship path, wording for the unnamed home field), d4e33ba (changeset words).
The delta touches 3 files (+61/-9): .changeset/27195-record-currency-door-names-home-field.md (1 sentence),
src/cube-record-currency-door.ts (the no-name remedy string only), src/tests/home-amount-consolidated-measure.test.ts.
No other file changed. analytics-service.ts and plugin.ts are byte-identical to e430075, and so is every line of the
door outside the homeRemedy else-branch. The envelope, codes, widening and declarations are unchanged.
The scope is still clean: no ui/, skills/, docs/adr/, .claude/ or packages/spec/src/** changes.

Prior items

  • M1 (status on every refusal path): RESOLVED. expect(...status).toBe(400) is now on every refusal case: grouped +
    totals (test :318), ungrouped dataset (already there), BARE no-homeAmount, inferred cube amount_sum, the new
    relationship-path case, and draft preview (:413). Ablation: setting every 400 expectation to 499 makes 22 of 36 fail,
    which is 11 refusal cases per cell × 2 cells. So each status assertion runs on a path that really refuses.
  • S1 (wording when a host does not relay the field): RESOLVED. The no-name branch now reads "point the measure at the
    home field of '' (the field its currencyConfig.homeAmount.field names on object ''; declare homeAmount
    there first if it has none)". That is true in both cases: an amount with no declaration, and a host that does not
    relay homeAmountField for an amount that has one. It is one branch, so one pin (BARE) covers both by
    construction. The changeset "Now." sentence now matches the code ("by name ... when the host relays it, or else as
    'the field its currencyConfig.homeAmount.field names', with the declaration to add if it has none"). The
    "widening" bullet ("a host that does not answer it gets the message without the field's name") still holds.
  • S2 (relationship-path prefix): RESOLVED, and the test is real. os27195_fx_line has a lookup to the invoice, and
    LINE_CUBE declares a join with invoice.amount / invoice.amount_home / invoice.currency_code. On both faces × both
    cells, the ungrouped invoice_raw is refused 400 with member/field invoice.amount/object, naming
    'invoice.amount_home' and 'invoice.currency_code', with zero reads. A native-face positive shows invoice_home served
    as 2010. Ablation: dropping ${prefix} from the home remedy turns 4 of 36 red, exactly the 4 relationship-refusal
    cases.
  • S3 (the "memory" cell): NOTED in the test docblock: the memory cell is the draft-preview evaluator, not driver-memory,
    with the census reason given.
  • S4 (CI skips the PG cell): unchanged and informational. The PG cell was run locally again (below).

New items

Should-fix (nit, non-blocking):

  1. cube-record-currency-door.ts:59-61. The module docblock still says "named when the amount declares
    currencyConfig.homeAmount, and the declaration to add when it does not". After S1 the unnamed case keys on whether
    the host relays the name, and it now points at the field homeAmount.field names before telling the author to
    declare one. This is internal module prose, not exported and not in the published contract. Fix it in passing if
    the PR is touched again.

No must-fix.

Checks run (fresh worktree at d4e33ba, npm_config_nodedir=/opt/node22, local PostgreSQL 16)

Check Exit Result
pnpm install --frozen-lockfile --filter "@objectstack/service-analytics..." 0
pnpm turbo run build --filter "@objectstack/service-analytics^..." 0 14/14
npx vitest run src/__tests__/home-amount-consolidated-measure.test.ts with OS_TEST_POSTGRES_URL 0 36/36
same file, no PG 0 18 passed / 18 skipped (pg cell named skip)
ablation: ${prefix} removed from home remedy 1 4 failed (only the relationship-refusal cases)
ablation: status expectations set to 499 1 22 failed
pnpm typecheck (service-analytics) 0
npx eslint on the 2 changed .ts files 0

CI at d4e33ba (gh api, read only): Check Changeset success. Most jobs were still in_progress when read, including
Lint & Repo Gates, TypeScript Type Check (pending) and Test Core. One "PR-body guards" run was cancelled, which looks
like a superseded duplicate. The seat must confirm Lint & Repo Gates and TypeScript Type Check are success at this
head before ready/enqueue.

Seat actions carried over (not dev items)

  • Claim 6640812575 still has the line-initial Clause-②: no. The PR and changeset declare yes (widening), and the
    diff confirms yes. Revise the claim before ready/enqueue: the enqueue gate reads only the claim's line-initial key.
  • Post the record of review in the matching form (## Contract review, Served-tier: TIER_DEFAULT, Head-sha:
    d4e33ba, ①②③, Local-runs:).

Local-runs: probe — proof file with and without PG plus 2 ablations, typecheck and eslint, on a throwaway worktree
(removed afterwards). Nothing pushed, no GitHub writes.

@objectstack-fleet

Copy link
Copy Markdown
Contributor

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

Seat ACCEPT · PR #30176 · Head-sha: d4e33ba109630f0fff336b35892cf7af71e108ed · 2026-11-04T21:26Z

维护者速读

  • 改了什么: 证明合并报表直接可用:USD/EUR/CNY 三种记录币种的行经写入投影进影子列后,对影子列 sum 在草稿预览、SQLite、Postgres 上都得出本位币合计(2010),产品代码零改动;未分组汇总原币金额被拒时,文案现在给两个药方:指向影子列(宿主透传时点名,如 amount_home、经关联的 invoice.amount_home),或按币种码字段分组。
  • 为什么改: 仪表盘是客户看合并数的地方;原文案只教按币种分组,不告诉作者合计该指向影子列。
  • 风险与回滚: 小。错误码、400、信封不变,只改文案;导出类型 AnalyticsServiceConfig.sourceFieldMeta 多一个可选键 homeAmountField(Clause-② 放宽,纯增量);patch changeset。回滚即撤 PR。
  • 先看: cube-record-currency-door.ts 的文案分支,再看 home-amount-consolidated-measure.test.ts。
  • 两分钟验证: OS_TEST_POSTGRES_URL=… npx vitest run src/__tests__/home-amount-consolidated-measure.test.ts 36/36。
  • 需要您做的: 无(非受管面,席位落地)。

席位意见:证明与文案都做到了卡上要求,放宽是纯增量,可落地。

席位核对

  1. 形态: service-analytics 5 个文件(+487/-17),三个提交,无 rebase;patch changeset,Metadata-protocol: none。
  2. 前提实测: 复审者在新 worktree 上本地 PG16 跑 36/36;消融:度量改指原币 10 红、去掉 homeAmount 22 红、在 main 上跑 14 红(全是文案断言)、去掉关联前缀 4 红、status 改 499 共 22 红。
  3. 门禁 check-runs: head d4e33ba1 success 36、skipped 15、cancelled 1(重复的 PR-body guards,同名另一跑 success)。check-governed-merges --pr 30176:NOT governed,504 行 ≤ 3000。
  4. 接受的偏差: Clause-② 由认领时的 no 改为 yes(widening),认领评论 6640812575 已改;复审记录 6641938881(REWORK)与 6642086901(增量 PASS),Served-tier TIER_DEFAULT。
  5. 范围外去向: 门模块头注释(59–61 行)仍是旧说法,非导出文字,下次触碰时顺手改;CI 跳过 Temporal Conformance,PG 格只在本地跑过。Skills-docs: none。
  6. 落地条款: 非受管面:本笔之后 ready + auto-merge;合并后摘 analytics: consolidated measures over the home field work unchanged (proof); a metric over a record-mode field must name the home field or group by code (multi-currency M3-1, epic #8345) #27195 的 pm:dispatched。

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 4, 2026 21:27
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 4, 2026
Merged via the queue into main with commit a35fc66 Nov 4, 2026
52 of 53 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-lsginq branch November 4, 2026 21:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/l tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Projects

None yet

2 participants