Skip to content

Commit 74a19dd

Browse files
docs(skills): the formula skill says the build refuses date subtraction (end - start) (#29122)
<!-- claude-projects-attribution: {"github_login":"huangyiirene"} --> _Requested by **Irene** · [project thread](https://claude.ai/code/project/chan_01TJXtQ1tVD26ecLDZxFS67x?thread=cmsg_01TJXtQ1tVD26ecLDZxFS67xUVqqk5nMQrFBKFQ5V87Lo5)_ Fixes #29061 Clause-②: no Metadata-protocol: none Blocked-by: #29058 ## 维护者速读 - 改了什么:formula 技能的「No date arithmetic」说明改为:构建拒收 `date + n` 和 `end - start`(日期或日期时间相减、`today()`/`now()` 减日期字段),处方是 `daysBetween(a, b)`;Want/Write 表不动。dashboards 规则、两个 eval 里写成日期相减的请假天数公式与说明同步改掉。 - 为什么改:#29058 让公式门禁拒收日期相减;旧说明写着 `end - start`「不报错、得到存成 `{}` 的 duration」,会教 AI 写出构建不过的公式。 - 风险与回滚:只改技能文字与 eval 文字,无代码;行数不变,token 净增 +26(两个受棘轮文件仍在上限内,上限未动)。必须在 #29058 合并后再落地,否则技能说的拒收在 main 上还不成立。回滚 = revert 本 PR。 - 先看:① `skills/objectstack-formula/SKILL.md` 第 151-153 行的说明;② `skills/objectstack-ui/rules/dashboards.md` 第 100 行;③ 两个 eval 的一行改动。 - 两分钟验证:`node scripts/check-skills-token-ratchet.mjs` 全绿,`skills/objectstack-formula/SKILL.md` 为 5686/5997。 - 需要你做的:#29058 合并后,审批并合并本 PR(Tier H `skills/**`)。 ## What changes Text only, inside the claim's file surface. The wording follows PR #29058 at head `e029ea772b1555ea33b2946e0978ff4175dbdd3e` (its gate in `packages/formula/src/validate.ts`, its refusal messages, its changeset and its new row in `content/docs/data-modeling/formulas.mdx`). - `skills/objectstack-formula/SKILL.md`, the "No date arithmetic" note. It said `end - start` "does not fault — it yields a `duration` stored as `{}`". It now says the build refuses both `date + n` and `end - start`: a date or date-time mixed with a number, or minus another, reads `null`, and `today()` / `now()` minus a date field reads `{}`. It prescribes `daysBetween(a, b)`. The Want / Write table is unchanged. - `skills/objectstack-ui/evals/analytics-inline-vs-dataset.json`, eval 6. The stored `days_requested` formula is restated from `end_date - start_date` to `daysBetween(record.start_date, record.end_date)`. Its only `must_contain` token, `number`, is unchanged and still taught by the skill. - Item 3 sweep (`skills/**`, then `content/docs/**`), two more hits, both fixed here: - `skills/objectstack-ui/rules/dashboards.md` line 100 described the same leave-days formula as `` `days_requested` (`end - start`) ``. It now reads `` (`daysBetween(start, end)`) ``. The rule it teaches (never `sum` a formula) is unchanged. - `skills/objectstack-formula/evals/cel-predicates-formulas.json` eval 4 explained `record.close_date - today()` as "a duration stored as `{}`", which reads as a shape that builds. It now says "the build refuses it". Assertions unchanged. - Outside `skills/**`: `content/docs/data-modeling/formulas.mdx` is updated by #29058 itself; `content/docs/releases/v16.mdx` line 726 is release history and stays as written. No other `content/docs/**` text subtracts dates in a formula. - `.changeset/29061-formula-skill-date-subtraction-refused.md`: `@objectstack/skills` patch. **Skills readings** (lines, then tokens from `check-skills-token-ratchet`): | | before | after | |:--|--:|--:| | `skills/objectstack-formula/SKILL.md` lines | 473 | 473 | | `skills/objectstack-ui/rules/dashboards.md` lines | 463 | 463 | | all `skills/*/SKILL.md` lines | 4550 | 4550 | | `skills/objectstack-formula/SKILL.md` tokens (ceiling 5997) | 5668 | 5686 | | `skills/objectstack-ui/rules/dashboards.md` tokens (ceiling 6262) | 6248 | 6251 | | eval fixtures tokens (not ratcheted) | 29160 | 29165 | | bundle total tokens | 222567 | 222593 | No ceiling moved and no line was re-wrapped. ## Tests No code changes. The claims in the new text were checked against the gate itself, by calling `validateExpression('value', …)` from a built `@objectstack/formula`, once at #29058's head (with this commit cherry-picked on top) and once at `origin/main` `a4015ab4`: <details><summary>#29058 head refuses every shape the note names and accepts every daysBetween shape; origin/main accepts the subtractions</summary> ``` -- #29058 head e029ea7 REFUSED date-arithmetic record.d + 1 REFUSED date-text-arithmetic record.end_date - record.start_date REFUSED date-text-arithmetic record.end_at - record.start_at REFUSED date-duration-value today() - record.start_date REFUSED date-duration-value now() - record.start_at REFUSED date-duration-value record.close_date - today() accepted daysBetween(record.start_date, record.end_date) accepted daysBetween(record.start_date, record.end_date) + 1 accepted daysBetween(today(), record.close_date) -- origin/main a4015ab REFUSED date-arithmetic record.d + 1 accepted record.end_date - record.start_date accepted record.end_at - record.start_at accepted today() - record.start_date accepted now() - record.start_at accepted record.close_date - today() accepted daysBetween(record.start_date, record.end_date) accepted daysBetween(record.start_date, record.end_date) + 1 accepted daysBetween(today(), record.close_date) ``` </details> The `origin/main` half is why this PR is `Blocked-by: #29058`: before that lands, the note would describe a refusal `main` does not make. `check:doc-formula-expressions` (it judges the formula examples in `skills/**` with the built `@objectstack/formula`) is green on both trees: on this branch at `a36bf3ab`, and on #29058's head with this commit cherry-picked (25 record-scoped examples across 548 files judged clean). ## Acceptance notes - The note says "never `end - start`" without carving out predicates. #29058 still accepts a condition that compares a duration (`today() - record.start_date > duration("72h")`). The skill keeps the simpler rule, which is never wrong for a formula; adding the carve-out would cost tokens for an edge the formula skill does not teach. - If #29058's head moves before it merges, this PR re-reads it. ## Verification Gates derived with `node scripts/pm/dispatch-gates.mjs --commands` (merge base `a4015ab47`, 5 paths, +18/−6) and run one by one on head `a36bf3ab`, the exit code captured before any pipe. <details><summary>45 derived, 41 exit 0, 4 NOT MEASURED (exit 3, whole-tree build prerequisite); --ran: 0 UNRUN</summary> ``` 41 commands :: exit 0, including node scripts/check-skills-token-ratchet.mjs :: exit 0 node scripts/check-skills-token-ratchet.mjs --self-test :: exit 0 node scripts/check-empty-changeset.mjs --base origin/main :: exit 0 node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0 node scripts/check-closing-keyword-parity.mjs :: exit 0 pnpm --filter @objectstack/lint run check:doc-formula-expressions :: exit 0 (after building formula + lint) pnpm --filter @objectstack/spec run check:skill-docs :: exit 0 pnpm check:skill-compatibility :: exit 0 pnpm check:skill-identifier-liveness :: exit 0 pnpm check:skill-frame-sync :: exit 0 pnpm check:nul-bytes :: exit 0 pnpm check:published-files :: exit 0 NOT MEASURED, exit 3 (PREREQUISITE NOT MET: every package's dist), left to CI: pnpm check:dts-closure pnpm check:dual-build-cjs-loads pnpm check:lean-entry-closure pnpm check:sourcemap-no-sources-content dispatch-gates --ran: 45 derived famil(ies) accounted for — 41 run, 4 NOT-MEASURED (4 DERIVED from a recorded exit 3). ``` </details> The four not measured are whole-tree families that read every package's `dist/`; this diff touches no package source, so a full local build was declared to CI rather than run. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01J8VVAjkjQLh4kZsuRAM4iV --- _Generated by [Claude Code](https://claude.ai/code/session_01J8VVAjkjQLh4kZsuRAM4iV)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent 67ee171 commit 74a19dd

5 files changed

Lines changed: 18 additions & 6 deletions

File tree

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
'@objectstack/skills': patch
3+
---
4+
5+
docs(skills): objectstack-formula says the build refuses date subtraction (`end - start`), and the skills stop describing a stored formula that subtracts dates
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
- **Before.** The "No date arithmetic" note in `objectstack-formula` said `end - start` "does not fault — it yields a `duration` stored as `{}`", which read as a shape that builds. `objectstack-ui` `rules/dashboards.md` and an analytics eval described a leave-days formula written as `end - start`, and a formula eval explained `record.close_date - today()` as "a duration stored as `{}`".
11+
- **Now.** The note says the build refuses both `date + n` and `end - start`: a date or date-time mixed with a number, or minus another, reads `null`, and `today()` / `now()` minus a date field reads `{}`. It prescribes `daysBetween(a, b)` and keeps the Want / Write table. The leave-days formula is written `daysBetween(start, end)` in the dashboards rule and `daysBetween(record.start_date, record.end_date)` in the eval prompt, and the formula eval says the build refuses `record.close_date - today()`.
12+
- The formula gate change itself ships in `@objectstack/formula` (`formula-date-subtraction-refused`). `SKILL.md` and `rules/dashboards.md` stay under their token ceilings (no ceiling moves). No schema, key, command or behaviour changes.

‎skills/objectstack-formula/SKILL.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -148,9 +148,9 @@ tests: every entry resolves at runtime, and this table documents them all.
148148
| `date(s)` / `datetime(s)` | timestamp | Parse an ISO date / date-time string to a timestamp |
149149
| `isoDate(t)` / `isoDatetime(t)` | string | Timestamp to UTC ISO text, `2026-10-08` / `2026-10-08T17:55:06.123Z`. Text in? `isoDate(date(s))` |
150150

151-
> **No date arithmetic.** A date mixed with a number faults and the build
152-
> rejects it; `end - start` does not fault — it yields a `duration` stored as
153-
> `{}`.
151+
> **No date arithmetic.** The build refuses `date + n` and `end - start`: a
152+
> date or date-time mixed with a number, or minus another, reads `null`, and
153+
> `today()` / `now()` minus a date field reads `{}`. Use `daysBetween(a, b)`.
154154
155155
| Want | Write — never `end - start`, `date + n`, `today() + 30` |
156156
|:---|:---|

‎skills/objectstack-formula/evals/cel-predicates-formulas.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
{
3535
"id": 4,
3636
"prompt": "Demo opportunities should close 45 days after the package is installed, and a formula should show how many days remain until close.",
37-
"expected_output": "Seed value as a CEL envelope, `close_date: cel`daysFromNow(45)``, never a JS `Date` / `Date.now()` (two `os build` runs must be byte-identical). Days remaining is `daysBetween(today(), record.close_date)` — never `record.close_date - today()` (a duration stored as `{}`) and never date arithmetic on `today()` (it faults).",
37+
"expected_output": "Seed value as a CEL envelope, `close_date: cel`daysFromNow(45)``, never a JS `Date` / `Date.now()` (two `os build` runs must be byte-identical). Days remaining is `daysBetween(today(), record.close_date)` — never `record.close_date - today()` (the build refuses it) and never date arithmetic on `today()` (it faults).",
3838
"files": [],
3939
"assertions": {
4040
"must_contain": ["cel`", "daysFromNow(", "daysBetween("],

‎skills/objectstack-ui/evals/analytics-inline-vs-dataset.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@
5555
},
5656
{
5757
"id": 6,
58-
"prompt": "leave_request shows days_requested, a formula (end_date - start_date, returnType 'number'), on its form; keep it. Add a KPI widget totalling days requested this quarter. Emit the metadata.",
58+
"prompt": "leave_request shows days_requested, a formula (daysBetween(record.start_date, record.end_date), returnType 'number'), on its form; keep it. Add a KPI widget totalling days requested this quarter. Emit the metadata.",
5959
"expected_output": "Never sums the formula (virtual, whatever its returnType): adds a stored number a beforeInsert/beforeUpdate hook keeps in step and sums that, or titles the widget as the count it is.",
6060
"files": [],
6161
"assertions": {

‎skills/objectstack-ui/rules/dashboards.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ Standardized answers to the recurring ambiguous cases:
9797
or boolean field (`percent` not under `sum`; `min`/`max` also dates); a `formula`
9898
has no column: refused whatever its `returnType` says. Store the number
9999
(user-entered, a `summary` rollup, or hook-kept) or title the widget the `count`
100-
it is. Leave days: `sum` over the `formula` `days_requested` (`end - start`) is
100+
it is. Leave days: `sum` over the `formula` `days_requested` (`daysBetween(start, end)`) is
101101
refused (`date` fields: no `duration`) ⇒ a stored `number` `days_count` a
102102
`beforeInsert`/`beforeUpdate` hook writes, then `{ name: 'days_sum', aggregate: 'sum', field: 'days_count' }`.
103103
- **Breakdown by a multi-value field.** `multiselect`/`checkboxes`/`tags` and

0 commit comments

Comments
 (0)