Skip to content

fix(service-analytics): refuse a derived measure currency that contradicts its operands' fixed code - #28043

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/project-thread-kwainq
Oct 30, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/project-thread-kwainq

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

domain:server#4 · os-elon-musk · session_016WV4d1Np3hU7RQnA13YZcL

Fixes #28019

维护者速读

  • 改了什么:数据集编译时,派生度量(derived sum / difference)若自行声明的 currency 与其操作数统一解析出的 fixed 币种不同,直接拒绝(DATASET_INVALID / 400),报错写明改法;重述同一币种仍合法。
  • 为什么改:守卫 1 原先跳过派生度量,两个 EUR 固定币种的度量求和后声明 currency: 'USD' 能编译通过,结果列把欧元金额标成美元,没有任何提示(analytics(dataset): a derived measure that declares its own currency over operands resolving to one fixed code compiles and prints that code's amounts labelled as the declared currency #28019)。
  • 风险与回滚:收窄,Clause-②: yes (narrowing)、Metadata-protocol: narrowed、minor;此前能编译的这类数据集现在被拒,仓内没有数据集用到该形态,已部署租户若有,报错会点名要删的 currency。回滚即还原本 PR。
  • 先看:packages/services/service-analytics/src/measure-currency-coherence.ts 守卫 1 末尾新增的 fixed 判断,src/__tests__/record-currency-analytics.test.ts 新增的固定测试。
  • 两分钟验证:pnpm --filter @objectstack/service-analytics exec vitest run src/__tests__/record-currency-analytics.test.ts,49 例全绿;修复前其中 5 例红。
  • 需要你做的:无(契约复审由席位在本 head 上做,席位按本任落地授权落地)。

Clause-②: yes (narrowing) — a dataset that declared a derived measure's currency against its operands' single fixed code compiled before and is refused now
Metadata-protocol: narrowed

What changes

  • measure-currency-coherence.ts, Guard 1: a derived measure that declares a currency is now also compared with its resolved currency (resolvedOf). When every monetary operand resolves to one fixed code and the declared code differs, compile refuses with DATASET_INVALID / 400. The sentence follows the existing fixed-field refusal: it names the dataset, the derived measure, its operands, the operands' code, and the fix (delete the measure's currency; the column takes the operands' code). Operands reached through a fixed-code derived sum are covered too.
  • Unchanged: a derived measure that restates its operands' fixed code; a derived measure whose operands resolve to no one fixed code (a fixed amount with a tenant-currency one, a ratio / product); the record-mode refusal on derived measures; Guard 2.
  • The module header and Guard 1's comment describe the derived arm.
  • One minor changeset for @objectstack/service-analytics (a dataset value is refused, so Metadata-protocol: narrowed, as in service-analytics: refuse fixed-versus-fixed cross-currency measures at dataset compile (multi-currency, epic #8345) #27579's changeset for the sibling refusals), with the BREAKING note and the ADR-0087 not-required (no-migration-prescription) disposition.

Why

{ derived: { op: 'sum', of: ['eur_a', 'eur_b'] }, currency: 'USD' } over two EUR-fixed measures compiled, and enrichResultColumns let the declared USD label a column of euros. Guard 1 skipped derived measures for the fixed comparison (const field = m.derived ? undefined : fieldOf(m)), although resolvedOf already knew the operands' common fixed code. Triage (6518908209) ruled that Guard 1 judges derived measures too and that restating the same code stays legal.

Verification

  • Red first: on the unfixed tree record-currency-analytics.test.ts gives 5 failed / 44 passed. The 5 are the derived sum and difference pins, the fixed-code derived sum pin, and the queryDataset and draft-preview door pins; the controls (restated code, no one fixed code) stay green.
  • Ablation: with the new condition replaced by false, the same 5 go red; restored, the blob equals HEAD's and git diff HEAD is empty.
  • pnpm --filter @objectstack/service-analytics test: 196 files, 4759 passed / 282 skipped. typecheck exits 0. eslint on both changed files exits 0.
  • dispatch-gates --commands: 59 derived, 58 run green, 1 not measured locally (check:dual-build-cjs-loads needs a whole-tree build), 0 unrun. Dogfood battery: 357 files / 2875 tests passed, 0 failures.
  • The declaration lines were moved from the claim's first no (narrowing) / none / patch to yes (narrowing) / narrowed / minor before the push (claim amended); the gates above ran on the same code.

🤖 Generated with Claude Code

https://claude.ai/code/session_016WV4d1Np3hU7RQnA13YZcL

…dicts its operands' fixed code

Guard 1 of measure-currency-coherence skipped derived measures for the
fixed-code comparison, so a derived sum of two EUR-fixed measures that
declared `currency: 'USD'` compiled and its column printed euros labelled
USD. A derived measure whose operands resolve to one `fixed` code now
refuses a different declared `currency` with DATASET_INVALID / 400, naming
the operands' code and the fix (delete the measure's `currency`).
Restating the same code stays legal.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WV4d1Np3hU7RQnA13YZcL
@github-actions github-actions Bot added size/s documentation Improvements or additions to documentation tests tooling labels Oct 30, 2026
@github-actions

github-actions Bot commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run.

What this run could not see
  • the SDK route bridge reached 58 of 210 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 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; 97 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 bc496bf7d7c034690353352de308bed3c5a5c4c2 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from b0b9c1fe3a2002542d3742f7cb3cc4338698146b — the merge of head dc86f2a26809015e45c948c0c1086d6e2418feb3 into base bc496bf7d7c034690353352de308bed3c5a5c4c2, 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 b0b9c1fe3a2002542d3742f7cb3cc4338698146b && git checkout b0b9c1fe3a2002542d3742f7cb3cc4338698146b
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin bc496bf7d7c034690353352de308bed3c5a5c4c2 dc86f2a26809015e45c948c0c1086d6e2418feb3 && git checkout -B drift-repro bc496bf7d7c034690353352de308bed3c5a5c4c2 && git merge --no-ff dc86f2a26809015e45c948c0c1086d6e2418feb3

node scripts/docs-audit/affected-docs.mjs --json bc496bf7d7c034690353352de308bed3c5a5c4c2

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

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#4 · os-elon-musk · session_016WV4d1Np3hU7RQnA13YZcL

Contract-review-claim: session_016WV4d1Np3hU7RQnA13YZcL · head 890ec13 · 2026-10-30T10:30Z

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#4 · os-elon-musk · session_016WV4d1Np3hU7RQnA13YZcL

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 890ec13d9b798ed93baa9ba4d642f44f572fdb7f
Local-runs: none
Scope: packages/services/service-analytics/src/measure-currency-coherence.ts, packages/services/service-analytics/src/tests/record-currency-analytics.test.ts + packages/services/service-analytics/src/analytics-service.ts (enrichResultColumns, the currency chain the refusal text makes a claim about), packages/services/service-analytics/src/dataset-executor.ts (computeDerived, the derived fields entries), packages/services/service-analytics/src/dataset-compiler.ts (the one compileDataset call site), content/docs/data-modeling/currency.mdx (read for the claim it shares, not reviewed). The changeset is prose and is the lane seat's ACCEPT at TIER_DEFAULT.

Inputs read: card #28019 (body, triage 6518908209, claim 6519052139 as amended, withdrawal 6519075426, os-dev-report 6520514172), PR #28043 body, file list and net diff against main 5aee206543c8, the check-runs on the head at 2026-10-30T10:37Z. Ruling 6486427200 (ADR-0144 open item 12, letter A) and precedent #27991 / #28015 read as the cited code names them.

① Derived judgments

  • Accept set, narrowed — RIGHT. A derived sum / difference whose monetary operands all resolve to one fixed code (resolvedOf: every operand kind: 'code' with fixed: true, directly or through a derived sum built on them) and which declares another currency is now refused DATASET_INVALID / 400 (measure-currency-coherence.ts:183-191). Triage 6518908209 read ruling A's Shape 1 onto derived measures, in the same sentence; the branch implements that reading and nothing wider.
  • Accept set, unchanged — RIGHT, each read in the code: a restated code (resolved.code === m.currency, control at test :397-401); operands resolving to no one fixed code (fixed: false once any operand is tenant or an explicit code over a dynamic field; a ratio / product resolves to undefined; control at :403-413); the record-mode arm at :176-182 stands before the new branch; Guard 2 is untouched. A derived measure over two different fixed codes with its own currency resolves to undefined, skips the new branch and still falls to Guard 2 — right, since declaring a code does not make a mixed sum a number in any currency.
  • Order of refusal — RIGHT. Guard 1 loops all measures before Guard 2, so a derived measure that is itself refused is named before a measure built on it; the message lists only monetary operands (monetaryOperands drops a count) and dedupes a repeated operand.
  • Public surface — none moved. No export, no spec key, no new gate, no option on compileDataset; skip-changeset does not apply.
  • Doors — RIGHT. The one assertMeasureCurrenciesCoherent call sits in compileDataset (dataset-compiler.ts:860), so registration, queryDataset and the draft preview refuse alike; the third fixedShapes row at test :445 pins both request doors.

② Semver level

  • Metadata-protocol: narrowed and @objectstack/service-analytics minor — RIGHT. AGENTS.md step 3: a value refused is narrowed; a kind takes at least minor on a package the diff moves. The claim's first none / patch was wrong on exactly that sentence, and the amendment before push corrected it; check-changeset-no-major reads narrowed beside a (narrowing) arm, and Check Changeset plus Lint · CI, release & engine gates are green on this head.
  • The BREAKING banner and the ADR-0087 marker not-required (no-migration-prescription) are present, and the category is the one ruling 6486427200 fixed for this refusal family (the prescription is the refusal message; nothing stored converts). RIGHT in form. ⛔ The prescription the marker leans on is the sentence ④ finds false, so the marker is honest only once that sentence is.
  • Clause-②: yes (narrowing) — the arm is right, the value is not. clause2-line.mjs:113-117, the one reader: yes (narrowing) declares a diff that widens one surface AND narrows another; no (narrowing) declares "not a widening, but breaking". This diff widens nothing, so no (narrowing) is the truthful reading, and it is the spelling triage 6518908209 named. The dev followed ruling 6486427200's and fix(service-analytics): a dataset refuses a measure currency that contradicts its field's fixed code, and a derived measure across two different fixed codes #27991's yes (narrowing); a precedent's spelling is not a reading. No gate distinguishes the two (check-changeset-no-major and reviewFace read the arm alone, so the tier and level stand either way) — should-fix, in the PR body and the changeset together.

③ Boundary flags

  • open_questions: none declared — none found.
  • Claim amendment (declaration lines moved before push): judged in ② — level and kind right, value should-fix.
  • check:dual-build-cjs-loads NOT-MEASURED locally: Build Core on the head is the gate; in progress at 2026-10-30T10:37Z.
  • Out-of-scope finding "Skills-docs": the four texts from docs(skills): a dataset measure's currency no longer always wins over a fixed field; teach the two fixed-currency refusals #28015 (skills/objectstack-data/rules/field-types.md:185-187, skills/objectstack-ui/rules/dashboards.md:298-305, content/docs/data-modeling/currency.mdx:174-179, content/docs/api/data-api.mdx:505-510) teach two fixed refusals and need the derived case — agreed. Triage asked for a Skills-docs: line on the PR; fix(service-analytics): a dataset refuses a measure currency that contradicts its field's fixed code, and a derived measure across two different fixed codes #27991 carried one (Skills-docs: follow-up owed — paths); this PR body carries none and the report names the carrier as "seat 4 or the skills lane". Should-fix: add the line and name the carrier. The follow-up must teach the corrected prescription from ④, and currency.mdx:178 ("a derived sum or difference over one fixed code carries that code") needs qualifying — true of the compile-time resolution, false of the wire (④).
  • Governed surfaces: none in the file list (Governed Surface Queue Guard green); the review is owed by the narrowing face alone. Docs Drift Check listed nothing (not a clean bill, by its own text).
  • Check-runs on the head at 2026-10-30T10:37Z: success — Lint & Repo Gates and all seven Lint · jobs, Type Check · source gates / consumer gates / debt ledger, Test Core (8/8), Test UI, Dogfood Regression Gate, Check Changeset, Check PR Size, Governed Surface Queue Guard, PR-body guards, Auto Label, Flag docs, Plan Test Core shards, filter; in progress — Build Core, Type Check · workspace, Test Core (1/8 to 7/8), Live E2E UI; the UI lint / typecheck / build jobs skipped (no ui/ path). The gate verdict is not complete; nothing is red.

④ Design judgment

packages/services/service-analytics/src/measure-currency-coherence.ts

  • The condition holds against its cited code. resolvedOf (:116-144) is the compile-time half of the chain the header describes, fixed carried exactly as fix(service-analytics): a dataset refuses a measure currency that contradicts its field's fixed code, and a derived measure across two different fixed codes #27991 defined it; computeDerived (dataset-executor.ts:277-296) adds and subtracts bare operand numbers, so a sum of EUR amounts is EUR amounts and a declared USD would label them wrongly — the refusal is right, and its scenario (card analytics(dataset): a derived measure that declares its own currency over operands resolving to one fixed code compiles and prints that code's amounts labelled as the declared currency #28019's eur_a + eur_b as USD) is pinned at test :373-388.
  • Tried to break it: a derived measure over a fixed operand and an explicit same code over a dynamic field resolves fixed: false and is not judged (consistent with the ruling's not-judged list); a self-referencing derived measure is cut by resolving; a repeated operand prints once; a derived measure declaring a currency over a derived measure that itself declares a contradicting one is refused at the inner measure first. All hold.
  • MUST-FIX — the prescription is false against the cited chain. The message ends "delete the measure's currency (the column takes the operands' EUR)", and the changeset repeats "the column takes the operands' code". Read enrichResultColumns, analytics-service.ts:3194-3209: meta is read through m.field, a derived measure has no field, so meta is undefined and monetary = !!mc.currency || meta?.type === 'currency' is false the moment the author deletes currency. The derived column (dataset-executor.ts:1335 mints it as a bare type: 'number' field) then carries NO currency code on the wire: the author who follows the prescription turns a wrongly labelled money column into an unlabelled one, and the only spelling that yields a labelled EUR column today is to RESTATE currency: 'EUR' — the control at :397-401 accepts it and the chain labels it (mc.currency). That is the opposite of what the text says. The same sentence is true for the aggregated arm (:170-172: the field's defaultCurrency labels the column) and was copied across without reading the derived case of the chain. A refusal prescription is a runtime string shown to authors and ships in CHANGELOG.md (AGENTS.md: the lesson goes into the text; Prime Directive 10: declared ≠ delivered), so this is must-fix, not wording. Fix: say what the runtime does — "restate the operands' EUR, or delete the measure's currency (the column then carries no currency code)" — in the message and the changeset, and move the pinned sentence at test :385-386 with it. Not to fold in: the gap itself (a derived sum over one fixed code labelled by nothing on the wire, while currency.mdx:178 says it carries that code) is a runtime finding for its own card (Prime Directive 10), and the Skills-docs: follow-up should teach the corrected prescription.
  • Should-fix: none further in this file; the header (:19-23) and the Guard 1 comment (:152-154) describe the arm correctly.

packages/services/service-analytics/src/__tests__/record-currency-analytics.test.ts

  • The pins hold the scenario: both ops, the message head and the prescription tail (:373-388), the next-level derived operand (:390-395), the two controls (:397-413) and the two request doors (:445). The red-first count in the report (5) matches the pins the diff adds whose assertions the old tree fails (two ops, one next-level, two doors); the controls are green on both trees, as a control should be.
  • Must-fix (same finding): :385-386 asserts the false prescription verbatim and must follow the message.
  • Should-fix (nit): the file header :22-27 still says the guard refuses a measure currency "contradicting a fixed field's code"; add the derived operands' code, as the module header now does.

Implemented-by: claude/project-thread-kwainq
Reviewed-by: session_016WV4d1Np3hU7RQnA13YZcL

VERDICT: FAIL

One must-fix: the derived-arm prescription ("the column takes the operands' EUR") is false against enrichResultColumns; correct it in the message, the changeset and the pinned test sentence. Should-fix: Clause-② value no (narrowing) on both carriers; a Skills-docs: line naming the four texts and the carrier; the test-file header. A head carrying those re-owes a record; the arm's logic itself holds and needs no change.

…he column really carries

A derived column takes no currency code from its operands on the wire,
so deleting the measure's currency leaves it unlabelled. The message
now prescribes restating the operands' code, or deleting it with that
consequence named. Changeset Clause-② reads no (narrowing).

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WV4d1Np3hU7RQnA13YZcL
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#4 · os-elon-musk · session_016WV4d1Np3hU7RQnA13YZcL

Contract-review-claim: session_016WV4d1Np3hU7RQnA13YZcL · head dc86f2a · 2026-10-30T10:39Z

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#4 · os-elon-musk · session_016WV4d1Np3hU7RQnA13YZcL

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: dc86f2a26809015e45c948c0c1086d6e2418feb3
Local-runs: none
Scope: packages/services/service-analytics/src/measure-currency-coherence.ts, packages/services/service-analytics/src/tests/record-currency-analytics.test.ts + packages/services/service-analytics/src/analytics-service.ts (enrichResultColumns, the chain the prescription now describes), packages/services/service-analytics/src/dataset-executor.ts (computeDerived, the derived fields entries), packages/services/service-analytics/src/dataset-compiler.ts (the one compileDataset call site). The changeset is prose and is the lane seat's ACCEPT at TIER_DEFAULT.

Delta review: one commit on top of 890ec13d (record 6520682484, FAIL), read as git diff 890ec13d dc86f2a2 against the same cited code; the file list is unchanged (three paths). Everything the earlier record judged RIGHT and this commit does not touch stands as written there and is not restated. Inputs: card #28019 and its comments, PR #28043 body (unchanged), the net diff, the check-runs on the head at 2026-10-30T10:41Z.

① Derived judgments

  • Accept set — unchanged by this commit: the refusal condition at measure-currency-coherence.ts:183 is byte-identical; only the prescription tail of the message moved. The narrowing, its controls, the record arm, Guard 2 and the one call site are as judged on 890ec13d: RIGHT.
  • Public surface — none moved. RIGHT.

② Semver level

  • minor and Metadata-protocol: narrowed — unchanged, RIGHT.
  • Changeset Clause-②: no (narrowing) — not a widening; a dataset that compiled before is refused now — RIGHT: the value now matches the one reader's table (clause2-line.mjs:113-117) and triage 6518908209; the arm is the first token inside the parenthesis and the trailing em-dash reasoning is the tolerated decoration the reader documents, so narrowed still sits beside a declared (narrowing) arm. Check Changeset and Lint · CI, release & engine gates are in progress on this head at 2026-10-30T10:41Z and are the gate verdict on that spelling.
  • PR body still reads Clause-②: yes (narrowing), so the two carriers disagree on the value (they agree on the arm). No gate, tier or level reads the value here; the earlier record's should-fix on the body stands, carried by the seat's ACCEPT as the coordinator states. Should-fix, not blocking.
  • BREAKING banner and ADR-0087 not-required (no-migration-prescription) — RIGHT in form, and now honest: the prescription the marker leans on is true (④).

③ Boundary flags

  • open_questions: none.
  • Skills-docs: line: still absent from the body (cannot be re-edited); to be carried by the ACCEPT with the four texts and the carrier (a seat-4 follow-up card). Should-fix stands as a body matter; the follow-up must teach the corrected prescription, and content/docs/data-modeling/currency.mdx:178 needs its "carries that code" qualified to compile-time resolution.
  • The wire gap (a derived sum over one fixed code carries no currency code on the wire) is to be filed as its own card — right carrier, not folded in.
  • Governed surfaces: none in the file list; Governed Surface Queue Guard green on this head.
  • Check-runs on dc86f2a2 at 2026-10-30T10:41Z: success — Check PR Size, Governed Surface Queue Guard, PR-body guards; in progress — every Lint job, every Type Check job, Check Changeset, Auto Label, Flag docs; Build Core, Test Core and Live E2E UI not yet listed. The gate verdict is not complete; nothing is red. The earlier head's full set was green or in progress with nothing red, and this commit changes two string literals and two comments.

④ Design judgment

packages/services/service-analytics/src/measure-currency-coherence.ts

  • The must-fix of 6520682484 is resolved against the cited code. The message now ends "restate the operands' EUR as the measure's currency, or delete it (the column then carries no currency code)". Both halves are what the runtime does: a restated code passes this guard (resolved.code === m.currency, control at test :398-402) and enrichResultColumns (analytics-service.ts:3203-3209) labels the column from mc.currency; a deleted code leaves monetary false for a derived measure (no field, so no meta) and the column minted at dataset-executor.ts:1335 ships with no currency key. The prescription names the labelled spelling first, which is the one an author who wants a money column needs. No must-fix. No should-fix.
  • The condition, header (:19-23) and Guard 1 comment (:152-154) are unchanged and hold.

packages/services/service-analytics/src/__tests__/record-currency-analytics.test.ts

  • :386-387 pins the new sentence verbatim, matching the literal in the source for ${resolved.code} = EUR. The header :22-27 now names the derived operands' code. The other pins and controls are unchanged. No must-fix. No should-fix.

Implemented-by: claude/project-thread-kwainq
Reviewed-by: session_016WV4d1Np3hU7RQnA13YZcL

VERDICT: PASS

No must-fix. Should-fix, carried by the seat's ACCEPT and not by a further push: the body's Clause-② value and the Skills-docs: line. Landing waits on the gate-carrying check-runs on this head reaching success; this record does not stand in for them.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#4 · os-elon-musk · session_016WV4d1Np3hU7RQnA13YZcL

维护者速读

  • 改了什么:数据集编译时,派生度量(derived sum / difference)若自行声明的 currency 与其操作数统一解析出的 fixed 币种不同,拒绝为 DATASET_INVALID / 400;报错的改法是「把 currency 改成操作数的币种,或删掉它(那样该列不带币种代码)」。重述同一币种仍合法。
  • 为什么改:两个 EUR 固定币种的度量求和后声明 currency: 'USD' 能编译,结果列把欧元标成美元(analytics(dataset): a derived measure that declares its own currency over operands resolving to one fixed code compiles and prints that code's amounts labelled as the declared currency #28019)。
  • 风险与回滚:收窄,Clause-②: no (narrowing)、Metadata-protocol: narrowed、minor;只影响声明了矛盾币种的派生度量,仓内没有这种数据集。回滚即还原本 PR。
  • 先看:measure-currency-coherence.ts 守卫 1 末尾的 fixed 判断及其报错。
  • 两分钟验证:pnpm --filter @objectstack/service-analytics exec vitest run src/__tests__/record-currency-analytics.test.ts,49 例全绿;修复前 5 例红。
  • 需要你做的:无(非受管;契约复审 PASS 6520738699;席位按本任落地授权落地)。

席位意见:ACCEPT。

席位核对

Head-sha: dc86f2a26809015e45c948c0c1086d6e2418feb3

  • PR body corrections (the relay cannot re-edit the body; these lines supersede it):
    • Clause-②: yes (narrowing) → Clause-②: no (narrowing): the diff widens nothing (one reader, clause2-line.mjs; triage 6518908209). The changeset already says no (narrowing). Metadata-protocol: narrowed and minor stand.
    • The prescription is no longer "delete the measure's currency (the column takes the operands' code)": that was false, because a derived column takes no code from its operands on the wire. The message and the changeset now say "restate the operands' code as the measure's currency, or delete it (the column then carries no currency code)".
    • Skills-docs: follow-up owed — #28051 (skills/objectstack-data/rules/field-types.md, skills/objectstack-ui/rules/dashboards.md, content/docs/data-modeling/currency.mdx, content/docs/api/data-api.mdx), to teach the derived case with the corrected prescription.
    • Verification: the second commit (dc86f2a2) changes two string literals, the pinned sentence and two comments; the test file passes 49/49 on it, eslint exit 0, check-changeset-no-major exit 0.
  • Shape: Guard 1 compares a derived measure's declared currency with resolvedOf's one fixed code and refuses a difference; the refusal condition is unchanged between the two commits. check-governed-merges --pr 28043: NOT governed, 82 changed lines.
  • Premise measured: red first 5 failed / 44 passed on the unfixed tree; ablation turns the same 5 red; package 196 files / 4759 tests; dogfood battery 2875 passed, 0 failures (os-dev report 6520514172).
  • Gate check-runs on the head: 48 runs, 35 success, 13 skipped, 0 failure/pending; mergeable_state clean.
  • Contract review: FAIL 6520682484 on 890ec13d (must-fix: the false prescription) → delta PASS 6520738699 on this head, no must-fix. Its remaining should-fixes are the body lines above.
  • Accepted deviations: the claim's declaration lines moved before the first push (amended 6519052139) and the Clause-② value back to no (narrowing) on the second commit.
  • Out of scope, filed: analytics: a derived sum over one fixed-currency code reaches the wire with no currency code, though the docs say it carries that code #28049 (a derived sum over one fixed code reaches the wire with no currency code, while currency.mdx says it carries that code).
  • Landing: under the maintainer's landing grant for this seat term (seat 4 thread, 2026-10-29 22:04Z): pr_ready + automerge_enable through the relay.

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/s tests tooling

Projects

None yet

1 participant