Repository navigation
Commit 36436fa
docs(skills): teach the derived-measure fixed-currency refusal beside the two fixed-currency refusals (#28431)
<!-- claude-projects-attribution: {"github_login":"os-project-manager"}
-->
_Requested by **Leehom** · [project
thread](https://claude.ai/code/project/chan_01TenzsRopZj4jMbvjX8uz1X?thread=cmsg_01TenzsRopZj4jMbvjX8uz1XCYhrp146Mk9BxnoRcGGQcD)_
Fixes #28051
Clause-②: no
Metadata-protocol: none
Cross-lane dispatch: `domain:skills` card dispatched and reviewed by
`domain:server#2` under the maintainer's 「座位之间可以协调,你有空你就可以派」 (claim
6545242148); the two devx pages ride as the sibling PR #28015 (#27994)
carried them.
## 维护者速读
- **改了什么**:两份 skills 和两页文档补上第三条 `fixed` 币种拒收:派生 `sum` / `difference`
的操作数全部落在同一个 `fixed` 币种时,度量自己声明另一个 `currency`
会在数据集编译时被拒(`DATASET_INVALID` / 400)。改法照运行时的原话:删掉度量的
`currency`(列取操作数的币种),或写同一币种。两页文档同时写明:这样的派生列在线上答复里也带那个币种(PR #28122
落地的行为,本任在 `main` 上实测)。
- **为什么改**:PR #28043 加了这条拒收,四处文字只教前两条;AI 按旧文给 EUR 固定币种的派生求和写上 `currency:
'USD'`,数据集会被拒而文档没说。
- **风险与回滚**:只改文字,无 schema、键或行为变化。两份 skills 的 token
天花板不动:每处新增都在同文件内删重复付账(`field-types.md` 删掉示例已给出的 `currency_code`
命名惯例;`dashboards.md` 删掉已退役的 `categoryGranularity` 括注,解析拒收与 Dataset-Bound
Widgets 段已有它)。回滚即撤本 PR。
- **先看**:`skills/objectstack-ui/rules/dashboards.md`
的数据集币种注释(L300-306),`skills/objectstack-data/rules/field-types.md` 的
Currency resolution 段(L174-177)。
- **两分钟验证**:`node scripts/check-skills-token-ratchet.mjs`
预期全绿(`field-types.md` 3207/3207,`dashboards.md` 6259/6262);对照
`packages/services/service-analytics/src/measure-currency-coherence.ts`
Guard 1 末尾的 `resolved?.fixed` 分支读四处文字。
- **需要你做的**:`skills/**` 是 Tier H 受管面:PR 留 draft,等你批准;契约复审由席位在本 head 上做。
## What changes
The shipped refusal, read from `measure-currency-coherence.ts` Guard 1
on `main` (and pinned in `record-currency-analytics.test.ts`): a derived
measure that declares a `currency` over measures whose amounts are fixed
to one code, with a different code, is refused `DATASET_INVALID` / 400;
the message ends `delete the measure's \`currency\` (the column takes
the operands' CODE), or restate CODE.` Each of the four texts now
teaches that case and that prescription in its own words, beside the two
`fixed` refusals PR #28015 taught:
- `skills/objectstack-data/rules/field-types.md` (Currency resolution):
"over a `'fixed'` one, or a derived sum/difference over one `'fixed'`
code, it may only restate that code, else `DATASET_INVALID`: delete it,
and the column takes the code". Reader: the agent writing a `currency`
field and then a dataset measure over it; the mistake prevented is
`currency: 'USD'` on a derived sum of EUR-fixed measures. Paid for by
deleting "(by convention `currency_code`)", which the example two lines
above already shows (`currencyField: 'currency_code'`).
- `skills/objectstack-ui/rules/dashboards.md` (the dataset currency
comment): "A `fixed` field, or a derived sum/difference over one `fixed`
code, refuses another `currency` (delete it, the column takes that code;
restating is fine)". Reader: the agent writing a dataset measure for a
dashboard; the same mistake. Paid for by deleting "(The old widget-level
`categoryGranularity` was removed in the single-form cutover.)": the
parse refusal names that key with its prescription
(`LEGACY_WIDGET_ANALYTICS_KEYS` in `dashboard.zod.ts`) and the
Dataset-Bound Widgets paragraph already says the closed schema rejects
the legacy keys by name, so the sentence had two homes.
- `content/docs/data-modeling/currency.mdx` (Roll-ups and datasets): a
new bullet for the derived case with the prescription, and the existing
sentence "a derived `sum` or `difference` over one `fixed` code carries
that code" now says "on its result column too": true on the wire since
PR #28122 (#28049), measured below.
- `content/docs/api/data-api.mdx` (the currency chain): the chain
paragraph gains the derived step (a derived `sum` / `difference` whose
operands all resolve to one `fixed` code takes that code at the field's
step; any other derived measure is monetary only by its own `currency`,
else its column carries none), and the refusal paragraph gains the
derived case with the prescription.
- `.changeset/28051-skills-derived-currency-refusal.md`:
`@objectstack/skills` patch. The order ruled `skip-changeset`;
os-dev.md's published-surface criterion wins over the order on a
conflict, and it is measured: `packages/skills/scripts/sync-catalog.mjs`
copies `skills/**` into that package's `dist/`, which is in its
`files[]`, and the sibling PR #28015 carried the same patch changeset.
Recorded in the report's `open_questions`.
### `skills/**` readings (lines, and tokens as the ratchet counts them)
| File | Before | After |
|:--|:--|:--|
| `skills/objectstack-data/rules/field-types.md` | 304 lines / 3191
tokens | 305 lines / 3207 tokens (ceiling 3207, headroom 0) |
| `skills/objectstack-ui/rules/dashboards.md` | 464 lines / 6261 tokens
| 464 lines / 6259 tokens (ceiling 6262, headroom 3) |
| all `skills/*/SKILL.md` | 4607 lines | 4607 lines (untouched) |
| whole catalog `skills/**/*.md` | 17850 lines / 782492 bytes | 17851
lines / 782547 bytes |
No ceiling row moves; `node scripts/check-skills-token-ratchet.mjs`
exits 0 on the head.
## Tests
No code changes. The wire behaviour the docs now teach was measured on
`origin/main` `5da3a8ce` in this worktree, after `pnpm --filter
'@objectstack/service-analytics^...' build` (lock VERDICT command-exit
0):
```
pnpm --filter @objectstack/service-analytics exec vitest run --maxWorkers=2 --reporter=verbose src/__tests__/record-currency-analytics.test.ts
✓ … > a derived measure column takes the currency its operands resolve to > native strategy: a sum or difference over one fixed code carries it; no other derived column gains one
✓ … > objectql strategy: a sum or difference over one fixed code carries it; no other derived column gains one
✓ … > the draft-preview path labels the same columns
✓ … > compile-time guards on measure currencies > a derived sum declaring a currency other than its operands' one fixed code is refused, naming the fix
✓ … > a derived difference declaring a currency other than its operands' one fixed code is refused, naming the fix
✓ … > CONTROL: a derived measure restating its operands' one fixed code is accepted
Test Files 1 passed (1)
Tests 60 passed (60)
```
The refusal pin asserts the message tail `delete the measure's
\`currency\` (the column takes the operands' EUR), or restate EUR.`; the
wire pin asserts `fields[].currency === 'EUR'` on `eur_twice` (sum) and
`eur_net` (difference) with no `currencyField`, and `currency` undefined
on every derived column whose operands resolve to no one `fixed` code
(`usd_and_tenant`, `tenant_twice`, `jpy_twice`, `eur_per_invoice`,
`eur_product`, `total_twice`).
## Acceptance notes
- The card's Wanted quoted the message as it stood in PR #28043 ("or
delete it, in which case the column carries no currency code"). PR
#28122 (#28049) changed both the wire and that sentence before this card
was dispatched; the order says to teach what landed, so the texts teach
"the column takes the operands' code".
- `content/docs/**` is not a governed surface, but it rides this Tier H
PR, so the whole PR waits for the maintainer's approval.
- No out-of-scope findings.
## Verification
Head `02980b19`.
- `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` on the final diff (5 files): 67 derived
families, every one run locally with its exit code captured before any
pipe (`cmd > log 2>&1 < /dev/null; EXIT=$?`). `--ran`: "67 derived, 66
run, 1 NOT-MEASURED, 0 UNRUN".
- 66 exit 0, including `check-skills-token-ratchet` (+ `--self-test`),
`check:skill-examples` (the edited `os:check` block in `dashboards.md`;
after `pnpm --filter '@objectstack/client-react...' build`, the
prerequisite it names), `check:docs`, `check:doc-anchors`,
`check:doc-authoring`, `check:docs-transcript-drift`,
`check-docs-section-name`, `check:pm-skill-id-lint`,
`check:skill-identifier-liveness`, `check:skill-frame-sync`,
`check:dts-closure`, `check:pm-governed-merges`, `check-empty-changeset
--base origin/main`, `check-adr-0087-registration --base origin/main`,
`check:published-files`, `check-changeset-no-major`.
- NOT MEASURED: `check:dual-build-cjs-loads`, exit 3 (needs every
package's `dist`; 68 packages unbuilt here). CI runs it.
- `node scripts/pm/check-governed-merges.mjs --branch
claude/project-thread-4snjmh`: "GOVERNED — landing tier: H(人合)",
`skills/**` ×2 (`field-types.md`, `dashboards.md`); `content/docs/**` ×2
and the changeset are off the register; size 47 changed lines, under the
human-merge threshold. Exit 3 (its GOVERNED verdict).
- eslint `--no-inline-config` on the four edited files: exit 0. The
repo-wide `pnpm lint` is CI's.
- Full dogfood battery: n/a (no `!` in the title, no `(narrowing)` arm).
- Head repo: internal (this branch is in `objectstack-ai/objectstack`).
## Human Minimum
- [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic.
(not touched)
- [x] 2. Every name I added uses the casing for its kind. (no names)
- [x] 3. No formatter ran; `pnpm lint` passes. (eslint
`--no-inline-config` on the four files: exit 0; the repo-wide run is
CI's.)
- [x] 4. One issue, one branch off an up-to-date `main`, one pull
request.
- [x] 5. A change that publishes carries a changeset, and both
declaration lines are filled in here and in the changeset.
- [x] 6. `packages/spec` touched: its generated artifacts are
regenerated and committed. (not touched)
- [x] 7. No hand edits to `content/docs/references/`,
`packages/*/CHANGELOG.md` or `content/docs/releases/`.
- [x] 8. Title, body and comments are in English.
- [x] The *Before Submitting* list in CONTRIBUTING.md is done.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01FEkQ5nqSVzKxNiy5ZH2o44
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 22663ca commit 36436fa
5 files changed
Lines changed: 35 additions & 12 deletions
File tree
- .changeset
- content/docs
- api
- data-modeling
- skills
- objectstack-data/rules
- objectstack-ui/rules
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
518 | 518 | | |
519 | 519 | | |
520 | 520 | | |
521 | | - | |
522 | | - | |
| 521 | + | |
| 522 | + | |
| 523 | + | |
| 524 | + | |
| 525 | + | |
523 | 526 | | |
524 | 527 | | |
525 | 528 | | |
526 | | - | |
| 529 | + | |
| 530 | + | |
| 531 | + | |
| 532 | + | |
527 | 533 | | |
528 | 534 | | |
529 | 535 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
178 | 178 | | |
179 | 179 | | |
180 | 180 | | |
181 | | - | |
182 | | - | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
183 | 187 | | |
184 | 188 | | |
185 | 189 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
170 | 170 | | |
171 | 171 | | |
172 | 172 | | |
173 | | - | |
174 | | - | |
175 | | - | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
176 | 177 | | |
177 | 178 | | |
178 | 179 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
282 | 282 | | |
283 | 283 | | |
284 | 284 | | |
285 | | - | |
286 | | - | |
| 285 | + | |
287 | 286 | | |
288 | 287 | | |
289 | 288 | | |
| |||
301 | 300 | | |
302 | 301 | | |
303 | 302 | | |
304 | | - | |
305 | | - | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
306 | 306 | | |
307 | 307 | | |
308 | 308 | | |
| |||
0 commit comments