Skip to content

docs(skills): a dataset measure's currency no longer always wins over a fixed field; teach the two fixed-currency refusals - #28015

Merged
os-zhuang merged 2 commits into
mainfrom
claude/project-thread-4nhwwo
Oct 30, 2026
Merged

os-zhuang merged 2 commits into
mainfrom
claude/project-thread-4nhwwo

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Elon · project thread

Fixes #27994

Clause-②: no
Metadata-protocol: none
Cross-domain exception: triage 6516910471 grants this one domain:skills PR the two devx pages content/docs/data-modeling/currency.mdx and content/docs/api/data-api.mdx; claim 6517255606 declares them.

维护者速读

What changes

Each file states, in its own words, the two fixed-currency refusals PR #27991 added to dataset compile (DATASET_INVALID / 400), each fix, and the two cases that stay unjudged:

  1. Shape 1: a measure currency other than its field's fixed code is refused. Fix: delete the measure's currency, and the column takes the field's code. Restating the code is accepted.
  2. Shape 2: a derived measure, any op, whose monetary operands resolve to two different fixed codes is refused. Fix: derive from measures in one currency, or drop the operand in the other.
  3. Not judged: a fixed code against the tenant currency; a measure currency over a dynamic field.
  • skills/objectstack-data/rules/field-types.md (Currency resolution): the sentence "Under 'fixed'/'dynamic' a measure's own currency wins" is replaced by the dynamic / fixed split, the two refusals and their fixes, and the not-judged tenant case.
  • skills/objectstack-ui/rules/dashboards.md (the dataset currency comment in the date-granularity example): the two refusals, their fixes and both not-judged cases follow the record-mode sentence.
  • content/docs/data-modeling/currency.mdx (Roll-ups and datasets): three bullets after the record-mode ones, including that a derived sum / difference over one fixed code carries that code, so a measure built on it is judged the same way.
  • content/docs/api/data-api.mdx (the currency chain): one paragraph after the chain.
  • .changeset/27994-fixed-currency-measure-refusals.md: @objectstack/skills patch.

skills/** token budget (standing path)

Ruling quoted (maintainer, 2026-10-27, skills-lane decision card, verbatim and untranslated): 「A:先削重复,余额按实测提高」; the gate-recognised path per ruling record 6513306068 on #27847.

  • field-types.md 3158 -> 3207 (+49). Paid down first: the Formula example's returnType comment, which restated the Calculated table's return types, becomes a pointer to that table.
  • dashboards.md 6221 -> 6262 (+41). Paid down first: the metric example's colorVariant comment no longer restates that the measure's format / currency drive the number (Widget options says it), and the measure example in the currency comment shrinks to its currency key (the defineDataset above shows the measure shape).
  • Both rows pinned AT the measurement, with the deletion and the ruling recorded beside each row in scripts/check-skills-token-ratchet.mjs.

Why

After #27991 (#27579, ruling 6486427200, ADR-0144 Open item 12 letter A) the four texts taught a rule the compiler now refuses for a contradicting fixed code, and they named only the record-mode refusals from #27338. Taught from the merged measure-currency-coherence.ts and record-currency-analytics.test.ts, not from the card summary.

Verification

Head 946f6b44 (commit 0776905e plus a plain merge of origin/main, which touches none of these files; the derivation is identical after the merge).

  • node scripts/pm/dispatch-gates.mjs --commands: 85 derived families, all run. --ran: "85 derived, 84 run, 1 NOT-MEASURED, 0 UNRUN". 84 exited 0, including check-skills-token-ratchet, check:skill-examples (the edited os:check block in dashboards.md), check:docs, check:doc-anchors, check:doc-authoring, check:docs-transcript-drift and check-docs-section-name. NOT MEASURED: check:dual-build-cjs-loads, exit 3 (needs every package's dist). CI runs it.
  • Docs build: pnpm turbo run build --filter=@objectstack/docs (the apps/docs/vercel.json production command), exit 0.
  • Artifact-roster families whose roster sits under a path of this diff: check-published-list-mirrors, check:changeset-fixed, check:engine-double-contract, check:i18n-stale-fill, check-affected-docs exit 0; check:published-readme-exports exit 3 (needs built dists, not measured).
  • The offline lychee link check was not run locally: this diff adds no link.

Human Minimum

  • 1. Schemas start as Zod; packages/spec holds no runtime logic. (not touched)
  • 2. Every name I added uses the casing for its kind. (no names)
  • 3. No formatter ran; pnpm lint passes. (No formatter ran; pnpm lint was not run locally, the diff carries no code. CI runs it.)
  • 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: its generated artifacts are regenerated and committed. (not touched)
  • 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_016WV4czgqyQbG9VgmyNYDNt

…e currency chain

A dataset measure's own `currency` no longer always wins over a `fixed`
field: compile refuses a code other than the field's, and a derived
measure over two different `fixed` codes, both DATASET_INVALID / 400.
Two skills files and two docs pages now state both refusals, their
fixes, and the two not-judged cases. The skills rows are paid down
by deleting restating comments; the remainder raises two token-ratchet
rows on the standing path.

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

Copy link
Copy Markdown
Contributor Author

domain:skills#2 · os-elon-musk · session_016WV4d1QkPVVzNz3FJqb2iS

Contract-review-claim: session_016WV4d1QkPVVzNz3FJqb2iS · head 946f6b4 · 2026-10-30T08:05Z

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:skills#2 · os-elon-musk · session_016WV4d1QkPVVzNz3FJqb2iS

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 946f6b443f3981844ff9e04f0f503cd67734b7ae
Local-runs: probe — node scripts/check-skills-token-ratchet.mjs run once on a detached worktree at the head, the one local run, to confirm the two moved rows pass; nothing else built, run or re-run
Scope: skills/objectstack-data/rules/field-types.md, skills/objectstack-ui/rules/dashboards.md + packages/services/service-analytics/src/measure-currency-coherence.ts (module header, resolvedOf, pairRefused, guard 1 :130-158, guard 2 :160-176), packages/services/service-analytics/src/tests/record-currency-analytics.test.ts (compile-time guards on measure currencies :281-420), packages/services/service-analytics/src/dataset-refusal.ts (datasetInvalidError, DATASET_INVALID / 400), packages/services/service-analytics/src/analytics-service.ts (enrichResultColumns :3203-3208, the runtime chain), packages/spec/src/ui/dataset.zod.ts (DerivedMeasureOp :223), scripts/check-skills-token-ratchet.mjs (header :120-141, rows :690 and :846), .changeset/27994-fixed-currency-measure-refusals.md, content/docs/data-modeling/currency.mdx :165-185 and content/docs/api/data-api.mdx :496-512 (the cross-domain pages, read sentence by sentence as the triage grants them)

① Derived judgments

Base of the PR is e199adfb7 (merge base with origin/main; origin/main has since moved to 35fc9b9ad, touching none of these six files). 6dc06b2e4 (#27991) and 1fa48009b (#27984) are both ancestors of the head, so the compiler the texts teach and the ratchet path they walk are the ones on main. The runtime the four texts must match, read from measure-currency-coherence.ts and its tests, not from the card: guard 1's fixed arm refuses a non-derived measure whose currency differs from its bare field's defaultCurrency (field.defaultCurrency !== m.currency), and accepts the same code (test :325); guard 2 refuses any derived measure (for (const m of dataset.measures) { if (!m.derived) continue; ... }, all four ops of DerivedMeasureOp tested at :358) whose two monetary operands both resolve to kind: 'code', fixed: true with different codes; resolvedOf makes a derived sum / difference carry its operands' common currency as fixed only when every operand's is, and a ratio / product resolve to nothing; an explicit currency over a dynamic field is a non-fixed code (test :379) and a tenant resolution is never compared (test :385). Both throw datasetInvalidError, which dataset-refusal.ts pins to DATASET_INVALID / 400 (tests :300-301, :410-411). The runtime column chain is unchanged: enrichResultColumns still takes mc.currency ?? meta?.defaultCurrency ?? context?.currency, so "a measure's own currency wins" stays true wherever compile lets the measure through. Every added or changed sentence, judged against that:

  1. field-types.md:184-188 (Currency resolution): "A dataset measure's own currency wins over a 'dynamic' field" — TRUE (the chain, and the not-judged dynamic case stated as the behaviour it has; test :329). "over a 'fixed' one it may only restate the code, else DATASET_INVALID (delete it)" — TRUE: guard 1 fixed arm, restating accepted, the fix the refusal names ("delete the measure's currency (the column takes the field's ...)"). "as is a derived measure over two different 'fixed' codes (derive from one currency or drop an operand)" — TRUE: guard 2, the fix the refusal names. "A 'fixed' code is not judged against the tenant's" — TRUE (test :385 usd_and_tenant). The old unconditional "Under 'fixed'/'dynamic' a measure's own currency wins" is gone. The HTTP status is not named (should-fix 2); the error code is. The dev's declared deviation holds: on the base the paragraph carries no record-mode refusal text, so "next to the record-mode ones" has nothing to sit beside in this file; the refusals sit where the false sentence was.
  2. dashboards.md:303-307 (the currency comment in the os:check block): "A fixed field refuses a different currency (delete it; restating is fine)" — TRUE (guard 1 fixed arm; the comment's register already says "refuses" for the record-mode arm without naming the code, so the file's own words are kept). "a derived measure refuses operands in two fixed codes (derive from one currency or drop one)" — TRUE for two different codes, which is the only reading under which operands are "in two codes"; the word "different" is absent where the other three texts carry it (should-fix 1). "Not judged: currency over a dynamic field, a fixed code against the tenant's" — TRUE, both cases explicit. The chain line kept above it ("measure currency → the field's defaultCurrency (fixed only) → tenant") remains the runtime chain. The sentence follows the record-mode sentence, as the card asks. The shortened opener "A money measure may set currency (ISO 4217), e.g. currency: 'USD'" loses the example's field, which the file still shows at :102 ({ name: 'days_sum', aggregate: 'sum', field: 'days_count' }) and in the Computed-column bullet at :90.
  3. currency.mdx:174-185 (Roll-ups and datasets), three bullets after the two record-mode bullets: bullet one ("whose currency differs from its field's fixed code ... DATASET_INVALID / 400: currency: 'USD' over a field fixed to EUR would label euros as dollars. Delete the measure's currency, and the column takes the field's code. A currency that restates the field's code is accepted") — TRUE sentence by sentence, including the probe-A example the module header gives. Bullet two ("whose monetary operands resolve to two different fixed codes ... whatever the operation. Derive it from measures in one currency, or drop the operand in the other. A derived sum or difference over one fixed code carries that code, so a measure built on it is judged the same way") — TRUE: resolvedOf carries a sum / difference as fixed when every operand is, test :366 (usd_twice then mixed) pins the next-level judgment; "whatever the operation" matches the four-op loop at :358. Bullet three ("Not judged: a fixed code against the tenant currency, which compile does not know, and a measure currency over a dynamic field, which wins as the top of the currency chain") — TRUE, the module header's own list and reasons.
  4. data-api.mdx:505-511, one paragraph after the chain: "Over a fixed field the measure's currency may only restate the field's code. A different code is refused when the dataset compiles, with DATASET_INVALID / 400: delete the measure's currency, and the column takes the field's code" — TRUE. "A derived measure whose monetary operands resolve to two different fixed codes is refused the same way, whatever the operation: derive it from measures in one currency, or drop the operand in the other" — TRUE. "Neither a fixed code against the tenant default nor a measure currency over a dynamic field is judged" — TRUE. The chain paragraph above it (:497-503) is untouched and stays correct as the column chain; the new paragraph bounds it at compile. The record-mode paragraph (:513-519) still follows, so the refusals sit next to the feat(spec,service-analytics): record-currency analytics — grouped answers name a per-row currency, the cube door refuses an ungrouped amount aggregate, compile guards on measure currencies #27338 ones.
  5. Over- and under-statement check across the four: none of the four claims refusal 1 for a derived measure (all four say "field" / "over a fixed field"), none claims guard 2 compares an explicit code over a dynamic field with a fixed one, none claims a count operand is compared (the record-mode bullet in currency.mdx keeps "an amount per invoice, stays legal"), and none names a relationship-path field. "Any op" / "whatever the operation" is true of the derived measure being judged; nesting through a ratio / product is not reachable (they resolve to nothing) and currency.mdx says only sum / difference carry a code. Nothing in the ruling's letter is missing from any file: refusal, fix, same-code acceptance (dashboards.md "restating is fine", field-types.md "may only restate"), and both not-judged cases.
  6. Stale teaching elsewhere: git grep -n -iE "currency.{0,30}wins|wins.{0,40}currency|own .currency. wins" over skills/** and content/docs/** at the head hits only the three rewritten lines (field-types.md:185 now scoped to 'dynamic', currency.mdx:184 "wins as the top of the currency chain" inside the not-judged bullet, data-api.mdx:498 the chain paragraph bounded by the new one). The card's own grep (measure.{0,40}currency|currency.{0,40}measure) outside the four files and content/docs/references/ hits only content/docs/ui/reports.mdx:339, which is about display formatting and teaches no precedence. No location still teaches the unconditional rule.
  7. Ratchet, field-types.md: base 12631 bytes → ceil(12631/4) = 3158, row 3158 (zero headroom); head 12827 bytes → 3207; row moved 3158 → 3207, +49 = the measured remainder, pinned AT the measurement. Pay-down: the Formula example's returnType comment 'number' | 'text' | 'boolean' | 'date' | 'currency' (money; own currencyConfig) became "see the Calculated table"; the Calculated table at :124 carries the same return-type list and "'currency' carries its own currencyConfig", so it was a restatement and no fact is lost. Genuine deletion, same file.
  8. Ratchet, dashboards.md: base 24879 bytes → 6220 against row 6221 (one token of headroom); head 25047 bytes → 6262; row moved 6221 → 6262, +41 = the measured count less the one token of headroom, pinned AT the measurement (the row comment's "ceiling +41, zero headroom" is exact; the file itself moved +42). Pay-downs: (a) the metric example's colorVariant comment "the measure's own format + currency drive the number" is said at :90 ("Format/currency → the measure's format/currency") and :341 ("Presentation lives on the measure (format / currency / polarity)"), so a restatement; (b) the measure example { name: 'revenue', aggregate: 'sum', field: 'amount', currency: 'USD' } shrank to currency: 'USD', the shape with field surviving at :102. No fact lost. The row comment's "(the dataset above shows the shape)" is imprecise: the defineDataset directly above shows only a count measure with no field (should-fix 3, comment text only).
  9. Both row comments quote the 2026-10-27 ruling verbatim and untranslated (「A:先削重复,余额按实测提高」, the same string the script's header :130 and 40 other rows carry) and cite record 6513306068; the PR body's budget section quotes the same string and names 6513306068; record 6513306068 on [Decision] skills token ratchet: price evals/** out (they never enter a customer context), and make the standing 「先削重复,余额按实测提高」 ruling the gate-recognised path for teaching text — or keep the per-raise ruling round #27847 (letter B) makes that path gate-recognised, and the header on the head (:127-141) states that path and that the deletion and the quote are checked by this record. Both conditions the header sets (deletion of restatement in the same file shown in the diff; the ruling quoted in the body) are met for both rows. The local probe passes: field-types.md 3207 / 3207 (+0), dashboards.md 6262 / 6262 (+0), 56 authored files within ceilings, exit 0.
  10. Docs gates: the diff's content/docs/** edits are exactly the two declared files; CI Build Docs, Check Documentation Links and Lint · docs & code guards are green on the head; no link is added, consistent with the dev's unrun offline lychee.
  11. Changeset and declarations: .changeset/27994-fixed-currency-measure-refusals.md names '@objectstack/skills': patch (the publishing package for skills/**, packages/skills/package.json; in the fixed group of .changeset/config.json), repeats Clause-②: no and Metadata-protocol: none, and its prose matches the diff. @objectstack/docs is private: true, so the two docs pages need no changeset; Check Changeset is green. The PR body carries Fixes #27994, Clause-②: no, Metadata-protocol: none, the cross-domain line citing triage 6516910471 and claim 6517255606, and six ## 维护者速读 lines in Chinese. The body's measurement commit 0776905e plus the plain merge is as described: the merge brought in nothing touching these files.
  12. File surface against claim 6517255606: the four claimed files, the changeset and the ratchet rows; the two pay-down hunks (field-types.md:249, dashboards.md:181) are outside the claimed line ranges but inside the claimed files and are the deletions the standing path requires. No breach.

② Semver level

@objectstack/skills patch. Prose-only correction of two published skill pages, the two docs pages carrying no package; no schema, key, export or behaviour change; the narrowing itself shipped in #27991 under its own declarations. Clause-②: no is right and no narrowing is present or declared. Patch is the correct level and the changeset says so.

③ Boundary flags

  • Seat report deviations: one (field-types.md has no record-mode text to sit beside) — verified and accepted (① item 1). open_questions: none. out_of_scope_findings: none.
  • Reviewer's out-of-scope observation, for the server lane and not a must-fix here: guard 1 computes const field = m.derived ? undefined : fieldOf(m) and then checks only a record resolution, so a derived measure that declares its own currency over operands resolving to one fixed code ({ derived: { op: 'sum', of: ['eur_a', 'eur_b'] }, currency: 'USD' }) compiles, and enrichResultColumns prints the euros as USD. The ruling's Shape 1 names "the aggregated field's fixed code", so the four texts are right not to claim this refusal; it is the runtime's gap, filed by whoever carries it (measure-currency-coherence.ts, no test at :305-335 covers it).
  • Author: PR user.login is objectstack-fleet[bot] (attribution comment names os-elon-musk); no non-bot login to record.
  • Contract-review-claim: comment 6518045231 names this session and this head; no competing claim; the dev report 6518027558 names head 946f6b44.
  • CI at review time (42 check runs on 946f6b44, 11 skipped by path filter): 31 success including Build Docs, Check Documentation Links, Check Changeset, Governed Surface Queue Guard, PR-body guards, Lint · docs & code guards, Lint · repo & tooling gates (the skills token ratchet's family), Lint · PM dispatch-gates, Lint · ESLint, Build Core, Test UI, Live E2E UI, every Type Check job, Test Core shards 2-4; Test Core (1/8) still in_progress. No failure. The final "all green" is owed by the landing gate; nothing in this diff feeds a Core test shard.
  • Draft PR, mergeable_state: blocked — Tier H human approval still owed after this record.

④ Design judgment

Rules face, per hits file:

  • skills/objectstack-data/rules/field-types.md — against the card's scenario (an AI author writing a dataset measure over a fixed amount, who must neither add a contradicting currency nor derive across two fixed codes, and must not be told the compiler judges what it does not) and measure-currency-coherence.ts on main: the paragraph now splits dynamic (measure code wins) from fixed (restate or omit, else DATASET_INVALID), names both fixes in the refusal's own terms, and keeps the tenant case explicitly unjudged. It teaches nothing the compiler does not do and withholds nothing the ruling binds. Holds, with should-fix 2.
  • skills/objectstack-ui/rules/dashboards.md — the os:check comment keeps the runtime chain, the record-mode refusal from feat(spec,service-analytics): record-currency analytics — grouped answers name a per-row currency, the cube door refuses an ungrouped amount aggregate, compile guards on measure currencies #27338 and adds the two fixed refusals, fixes and both not-judged cases in the same register (no codes, as the file's record-mode sentence); an author copying the example writes currency only where it is legal. "two fixed codes" reads as two distinct codes; the missing "different" is a precision gap, not a wrong instruction. Holds, with should-fix 1.

No file in hits is a schema or ADR, so no first-consumer or execution-plan judgment is owed. The two docs pages are outside the review face and were read only as the triage's cross-domain grant and the card's "one rule in four places" require; every sentence of theirs is judged true in ① items 3-4 for the lane seat's ACCEPT.

Implemented-by: claude/project-thread-4nhwwo
Reviewed-by: session_016WV4d1QkPVVzNz3FJqb2iS

VERDICT: PASS

Must-fix: none.

Should-fix:

  1. skills/objectstack-ui/rules/dashboards.md:305 "a derived measure refuses operands in two fixed codes" — insert "different" ("in two different fixed codes") to match the other three texts and guard 2's a.code !== b.code; about 2 tokens over a zero-headroom row, so either a same-file trim of 2 tokens or a +2 row move on the same standing path, as a follow-up.
  2. skills/objectstack-data/rules/field-types.md:186 names DATASET_INVALID without the / 400 the two docs pages and the ruling carry; the sibling skill names neither. Optional, about 2 tokens, same path as item 1; an AI author matches on the code, so this is precision rather than a missing instruction.
  3. scripts/check-skills-token-ratchet.mjs:842 row comment "(the dataset above shows the shape)" — the defineDataset directly above the comment carries only a count measure with no field; the money-measure shape with field lives at dashboards.md:102. Comment text only; the arithmetic and the quoted ruling on both rows are exact.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:skills#2 · os-elon-musk · session_016WV4d1QkPVVzNz3FJqb2iS

维护者速读

  • 改了什么:四处文字按 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(service-analytics: refuse fixed-versus-fixed cross-currency measures at dataset compile (multi-currency, epic #8345) #27579,你的裁决 6486427200,ADR-0144 Open item 12 字母 A)改写数据集币种规则。① 度量的 currency 与所聚合字段的 fixed 币种不同,编译拒收(DATASET_INVALID / 400),修法是删掉度量的 currency;写同一个币种照常合法。② 派生度量的金额操作数分别落到两个不同的 fixed 币种,编译拒收,修法是只从同一币种的度量派生,或去掉另一币种的操作数。另写明两种不判的情形:fixed 币种与租户币种不比较;dynamic 字段上的度量 currency 不比较。涉及 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,外加 changeset 和棘轮两行,共 6 个文件。
  • 为什么改:四处都还教「度量自己的 currency 一律优先」。AI 作者照此写出的数据集,现在会被编译拒收。
  • 风险与回滚:只改文字,不动代码。两份文档在分诊批准的跨域例外之内(6516910471)。棘轮走常设路径:field-types 3158→3207,dashboards 6221→6262,都是同文件先删复述,并引了 10-27 裁决和 6513306068。revert 即回滚。
  • 先看:field-types.md 第 185 行起,再看 currency.mdx 的 Roll-ups and datasets 一节。
  • 两分钟验证:node scripts/check-skills-token-ratchet.mjs exit 0;文档构建与链接检查在 CI 上为绿。
  • 需要你做的:skills/** 是受管面,登记册判 Tier H,请批准本 PR(契约复核 PASS 记录在本评论之前,Head-sha: 946f6b443f3981844ff9e04f0f503cd67734b7ae)。

席位意见:ACCEPT。契约复核 PASS,没有 must-fix:四处文字逐句对照 main 上的编译代码和测试,没有说多也没有说少。三条 should-fix 都是措辞,不阻塞,留到下次改这些文件:dashboards.md:305 补「different」,field-types.md 补 / 400,棘轮一行注释的指向。复核还发现一个运行时缺口:派生度量自己声明 currency,而操作数都落到同一个 fixed 币种时,编译放行,结果把欧元标成美元。已另立 #28019 交 server 车道;本 PR 的文字没有声称这条拒收,所以仍然正确。CI 无失败。

席位核对

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 tooling

Projects

None yet

2 participants