Skip to content

docs(skills): teach the derived-measure fixed-currency refusal beside the two fixed-currency refusals - #28431

Draft
objectstack-fleet[bot] wants to merge 2 commits into
mainfrom
claude/project-thread-4snjmh
Draft

objectstack-fleet[bot] wants to merge 2 commits into
mainfrom
claude/project-thread-4snjmh

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Leehom · project thread

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 fix(service-analytics): a derived sum or difference over one fixed currency code carries that code on its column #28122 落地的行为,本任在 main 上实测)。
  • 为什么改:PR fix(service-analytics): refuse a derived measure currency that contradicts its operands' fixed code #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 twofixed` 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 fix(service-analytics): a derived sum or difference over one fixed currency code carries that code on its column #28122 (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), 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 docs(skills): a dataset measure's currency no longer always wins over a fixed field; teach the two fixed-currency refusals #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'oneur_twice(sum) andeur_net(difference) with nocurrencyField, and currencyundefined on every derived column whose operands resolve to no onefixed code (usd_and_tenant, tenant_twice, jpy_twice, eur_per_invoice, eur_product, total_twice`).

Acceptance notes

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

  • 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. (eslint --no-inline-config on the four files: exit 0; the repo-wide run is CI's.)
  • 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_01FEkQ5nqSVzKxNiy5ZH2o44

… the two fixed-currency refusals

A derived sum or difference whose operands all resolve to one fixed code
refuses another currency at compile (DATASET_INVALID / 400); the
prescription is the shipped message's: delete the measure's currency (the
column takes the operands' code), or restate it. The two skill texts and
the two docs pages teach it in those words, and the docs say the derived
column carries that code on the wire too, as main now does.

The skills token ratchet is paid by deleting a convention already shown in
the example above it (field-types.md) and a retired-key parenthetical the
parse refusal and the Dataset-Bound Widgets paragraph already carry
(dashboards.md).

Claude-Session: https://claude.ai/code/session_01FEkQ5nqSVzKxNiy5ZH2o44
Co-authored-by: Claude <noreply@anthropic.com>
…refusal texts

The skills catalog ships in @objectstack/skills (its build copies skills/**
into dist), so a skills text change publishes and takes a patch changeset.

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

Copy link
Copy Markdown
Contributor Author

domain:server#2 · os-project-manager · session_0186vfjHHMsRXC9EUT9hTFMo

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 02980b195a47fed090d71dfc443a0e7f588cb34b
Local-runs: none
Scope: skills/objectstack-data/rules/field-types.md, skills/objectstack-ui/rules/dashboards.md + packages/services/service-analytics/src/measure-currency-coherence.ts (Guard 1, measureCurrencyResolver), packages/services/service-analytics/src/analytics-service.ts (enrichResultColumns, the currency chain), packages/services/service-analytics/src/tests/record-currency-analytics.test.ts (the derived-currency pins), packages/spec/src/ui/dashboard.zod.ts (LEGACY_WIDGET_ANALYTICS_KEYS), content/docs/data-modeling/currency.mdx, content/docs/api/data-api.mdx, .changeset/28051-skills-derived-currency-refusal.md, scripts/check-skills-token-ratchet.mjs (the two rows)

Read-only shape held: the diff of origin/main...02980b195a47fed090d71dfc443a0e7f588cb34b (five files, +35/-12), card #28051 and its triage, the dev report (comment 6545456734), the order, sibling PR #28015 (#27994, merged), PR #28043 (#28019, merged, the refusal) and PR #28122 (#28049, merged, the wire column), and the cited code at origin/main. Nothing was built or run locally; the token counts below are the ratchet's own convention (ceil(utf8 bytes / 4)) applied to the blobs as read.

① Derived judgments

The PR moves no published schema, export or runtime: packages/** is untouched. The accepted set it changes is the published skills catalog (@objectstack/skills ships skills/** as dist/skills/), plus two devx pages. Each sentence the diff adds, against the code on main:

  1. The third fixed refusal exists and is stated as the code throws it — correct. measure-currency-coherence.ts Guard 1, last arm: a measure with m.derived and m.currency whose resolvedOf is { kind: 'code', fixed: true } with code !== m.currency is refused datasetInvalidError (DATASET_INVALID / 400) with the tail "delete the measure's currency (the column takes the operands' CODE), or restate CODE." resolvedOf carries fixed only for a derived sum / difference whose monetary operands all resolve to fixed codes (a ratio / product resolves to none), so the skills' and docs' scoping to "a derived sum/difference over one fixed code" is the guard's own scoping. Pinned by "a derived sum/difference declaring a currency other than its operands' one fixed code is refused, naming the fix" and the restating CONTROL in the test file.
  2. The prescription is the shipped one, not the card's older quote — correct. The card's Wanted ("or delete it, in which case the column carries no currency code") quoted PR fix(service-analytics): refuse a derived measure currency that contradicts its operands' fixed code #28043's first message; PR fix(service-analytics): a derived sum or difference over one fixed currency code carries that code on its column #28122 changed the wire and that message before dispatch. The order ruled "teach what its landing put on main", and all four texts teach "delete it, the column takes the operands' code; or restate it". Judged against the merged message text, this is right and the card's quote is stale by design.
  3. "carries that code, on its result column too" (currency.mdx) and "takes that code at the field's step" (data-api.mdx) — correct. enrichResultColumns on main: derivedCode = m.derived ? currencies.fixedCodeOf(m) : undefined; monetary = !!mc.currency || amount || derivedCode !== undefined; resolved = mc.currency ?? meta?.defaultCurrency ?? derivedCode ?? context?.currency. A derived measure has no field, so meta is undefined and the derived code sits exactly where the field's fixed code would. Pinned by eur_twice / eur_net carrying EUR on both strategies and the draft preview.
  4. "any other derived measure (a ratio, a product, operands in the tenant's currency) is monetary only by its own currency, else its column carries none" (data-api.mdx) — correct, and worth the sentence. For such a measure derivedCode is undefined, so without its own currency the column is not monetary and the chain is not entered: it does not fall through to the tenant default. The test pins no currency on usd_and_tenant, tenant_twice, jpy_twice, eur_per_invoice, eur_product, total_twice. The earlier text left a reader to assume the tenant step applied; the new sentence closes that.
  5. The two existing refusals and the two not-judged cases are left as PR docs(skills): a dataset measure's currency no longer always wins over a fixed field; teach the two fixed-currency refusals #28015 taught them — correct. Guard 1's field arm, Guard 2's two-fixed-codes arm, and the header's "Not judged" list (a fixed code against the tenant's; a measure currency over a dynamic field) are unchanged on main and restated unchanged in all four texts.
  6. The four texts agree with each other. Same trigger (derived sum / difference, operands all one fixed code, a different currency), same code (DATASET_INVALID / 400), same prescription (delete, column takes the operands' code; or restate) in field-types.md, dashboards.md, currency.mdx and data-api.mdx. No text claims the record-mode or two-codes refusal for the derived-currency shape, and none claims a ratio / product is judged.
  7. Nothing taught that would mislead a third-party agent installing the published skills. The two skills deletions are duplicates, not rules: field-types.md drops "(by convention currency_code)" while its own example two lines up shows currencyField: 'currency_code'; dashboards.md drops "The old widget-level categoryGranularity was removed in the single-form cutover", and the same file's Dataset-Bound Widgets paragraph says the closed schema rejects the pre-ADR-0021 inline keys by name, while LEGACY_WIDGET_ANALYTICS_KEYS in dashboard.zod.ts lists categoryGranularity and refuses it with the prescription an agent needs. An agent that still writes the key is told at parse what to write instead; no rule is lost. The skills diff carries no #NNNN issue number (id lint).
  8. Ratchet arithmetic holds. field-types.md: 12761 bytes / 304 lines on main to 12826 bytes / 305 lines on the head, ceil(12826 / 4) = 3207 against the row's 3207 (at ceiling, headroom 0); dashboards.md: 25043 to 25033 bytes, 464 lines both sides, ceil(25033 / 4) = 6259 against 6262. Neither row in scripts/check-skills-token-ratchet.mjs moves, matching the dev's readings. The one added line in field-types.md is paid for inside the same paragraph, not by re-wrapping.
  9. Check-runs on the head, as read: Build Core, Check Changeset, Governed Surface Queue Guard, Check PR Size, Check Documentation Links, Dogfood Regression Gate, PR-body guards and the scoped lint shards are success; Type Check (four shards), Test Core (1–3/8), Lint · ESLint, Lint · repo & tooling gates, Lint · docs & code guards, and Lint · CI, release & engine gates were still in_progress. The ratchet and check:skill-examples run in the in-progress lint shards; the landing-time all-green rule is the seat's to confirm, not this record's.

② Semver level

.changeset/28051-skills-derived-currency-refusal.md: '@objectstack/skills': patch, Clause-②: no, Metadata-protocol: none. Consistent with the PR body's two declaration lines, and the right package: packages/skills/package.json ships dist in files[], and scripts/sync-catalog.mjs copies skills/** into dist/skills/, so this diff publishes. The order's skip-changeset ruling conflicts with AGENTS.md Post-Task step 3 ("never skip-changeset: that label is for a diff that publishes nothing from any released package") and with os-dev.md's own criterion (the published face is what each package's files[] ships), which the order says wins on conflict. The sibling PR #28015 landed the same way, with .changeset/27994-fixed-currency-measure-refusals.md at patch. patch is the right level: no key, value or behaviour changes; a docs-only catalog change. The changeset text names the three refusals correctly and records the two paid-for deletions. Check Changeset is green on the head. No (narrowing) arm, no BREAKING banner, no ADR-0087 disposition owed.

③ Boundary flags

  • Dev open_questions (changeset vs skip-changeset) — answered: A, keep the @objectstack/skills patch changeset and apply no skip-changeset label. Reasons in ② above. The order's ruling was wrong on this line and the dev was right to follow the repo rule, as os-dev.md told it to; recorded here so the seat does not re-apply the label at landing.
  • The card's Wanted quote vs the shipped message — not a flag for this PR: the order already ruled "teach what landed"; see ① item 2. The card's text can stay as filed; the review record is the trail.
  • Author: PR user.login is objectstack-fleet[bot]; no relay-stripped human login to name.
  • Tier H: skills/** is governed; the PR is draft and awaits the maintainer's own approval. This record satisfies the contract review; it does not stand in for that approval, and nothing here arms auto-merge.
  • Not a flag, noted for the next card in this family: neither skill teaches Guard 1's record-mode arm on a derived measure (a currency on a derived sum of record-mode amounts is refused too; currency.mdx does say so). That text predates this card and is outside its Wanted; it is not a must-fix here.

④ Design judgment

skills/objectstack-data/rules/field-types.md (Currency resolution). Reader: the agent that declares a currency field and then writes a dataset measure over it. The added clause "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" holds against Guard 1 on main in both arms (field and derived) and against the test's refusal and CONTROL pins. The sentence it joins still says a measure currency wins over a 'dynamic' field and that a 'fixed' code is not judged against the tenant's, both true of the header's "Not judged" list. Against the card's eval shape (currency: 'USD' on a derived sum of EUR-fixed measures), an agent reading this paragraph now knows the dataset will be refused and what to write. The deletion ("by convention currency_code") is covered by the example on the same page. Holds.

skills/objectstack-ui/rules/dashboards.md (the dataset currency comment in the os:check block). Reader: the agent writing a dataset measure for a dashboard widget. "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); a derived measure refuses operands in two fixed codes (derive from one currency or drop one). Not judged: currency over a dynamic field, a fixed code against the tenant's." Each clause maps to one arm of the two guards on main, with the shipped prescription; the chain line above it (measure currency → the field's defaultCurrency (fixed only) → tenant) stays true because the derived code enters at the field's slot and the comment does not claim otherwise. The block is comment-only inside unchanged TypeScript, so check:skill-examples has nothing new to compile. The deleted categoryGranularity parenthetical was a historical aside, not a rule; its reader is caught at parse by the schema's named refusal. Holds.

The two devx pages (currency.mdx, data-api.mdx) are outside the governed register but ride this PR; read sentence by sentence in ① items 3 and 4, they match enrichResultColumns and the resolver on main and agree with the skills. No finding.

The changeset names the right package and level (②) and its prose matches what the diff does.

No must-fix. No delta review owed: both commits on the branch (1e38614ed the texts, 02980b195 the changeset) are covered by this one head.

Implemented-by: claude/project-thread-4snjmh
Reviewed-by: session_0186vfjHHMsRXC9EUT9hTFMo

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#2 · os-project-manager · session_0186vfjHHMsRXC9EUT9hTFMo

席位核对

ACCEPT · domain:server seat 2 · os-project-manager · session_0186vfjHHMsRXC9EUT9hTFMo · 2026-10-31T15:48Z · head 02980b19 · #28051

The maintainer's standing authorisation for landing reviewed PRs, verbatim: 「按照 pm-dispatch 规则复核过的都授权」. This PR is Tier H: that authorisation does not reach it. It stays draft until the maintainer approves.

  1. Shape:
    • A draft against main with no assignees, Fixes #28051, and a patch changeset for @objectstack/skills. No skip-changeset label.
    • check-governed-merges --pr 28431 reads ⛔ GOVERNED, skills/** ×2, 47 changed lines. Landing tier H(人合).
    • The claim (6545242148) names this PR's branch, claude/project-thread-4snjmh.
  2. Against the order:
    • The third fixed-currency refusal is now taught. Both skills files and both docs pages describe the derived-measure refusal as main throws it: a derived sum / difference whose currency differs from its operands' one fixed code answers DATASET_INVALID / 400.
    • The prescription is the shipped one. "Delete the measure's currency, and the column takes the operands' code, or restate that code." The card's older quote predates PR fix(service-analytics): a derived sum or difference over one fixed currency code carries that code on its column #28122, and the order ruled to teach what main carries.
    • Skills and docs agree. The deletions paying the token ratchet are true duplicates; no ceiling moved.
    • The open question is answered A: keep the changeset. The order's skip-changeset ruling was wrong, because skills/** ships inside @objectstack/skills. The seat has corrected its order template.
  3. Contract review: owed at CONTRACT_REVIEW_TIER (governed rules text). PASS on this head, 6545521434.
  4. Gates:
    • Locally, the dev ran 67 derived families: 66 exit 0 and 1 NOT MEASURED (check:dual-build-cjs-loads, which needs a full build; CI runs it).
    • CI on head 02980b19: 32 success, 12 skipped, 0 red. The state is clean.
  5. Overlap: PR fix(cli): os migrate record-currency-codes judges --code before the probe, prompt and boot; document the command and the record-mode switch #28416 also edits content/docs/data-modeling/currency.mdx, in a different section (a new "Switching a field that already holds rows" section near L122; this PR edits the refusal list near L178). Whichever lands second may need a rebase, with no expected conflict.
  6. Next:
    • ⛔ No seat flips it ready, enqueues it or arms auto-merge. The maintainer's approval (or merge) is the review record.
    • After it merges, the landing record goes on the card and the claim is released.

Generated by Claude Code

This branch has not been deployed

No deployments
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

Development

Successfully merging this pull request may close these issues.

skills/docs: the fixed-currency refusal texts teach two refusals and miss the derived-measure one (#28019)

1 participant