Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .changeset/29061-formula-skill-date-subtraction-refused.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
'@objectstack/skills': patch
---

docs(skills): objectstack-formula says the build refuses date subtraction (`end - start`), and the skills stop describing a stored formula that subtracts dates

Clause-②: no
Metadata-protocol: none

- **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 `{}`".
- **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()`.
- 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.
6 changes: 3 additions & 3 deletions skills/objectstack-formula/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,9 +148,9 @@ tests: every entry resolves at runtime, and this table documents them all.
| `date(s)` / `datetime(s)` | timestamp | Parse an ISO date / date-time string to a timestamp |
| `isoDate(t)` / `isoDatetime(t)` | string | Timestamp to UTC ISO text, `2026-10-08` / `2026-10-08T17:55:06.123Z`. Text in? `isoDate(date(s))` |

> **No date arithmetic.** A date mixed with a number faults and the build
> rejects it; `end - start` does not fault — it yields a `duration` stored as
> `{}`.
> **No date arithmetic.** The build refuses `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 `{}`. Use `daysBetween(a, b)`.

| Want | Write — never `end - start`, `date + n`, `today() + 30` |
|:---|:---|
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
{
"id": 4,
"prompt": "Demo opportunities should close 45 days after the package is installed, and a formula should show how many days remain until close.",
"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).",
"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).",
"files": [],
"assertions": {
"must_contain": ["cel`", "daysFromNow(", "daysBetween("],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
},
{
"id": 6,
"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.",
"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.",
"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.",
"files": [],
"assertions": {
Expand Down
2 changes: 1 addition & 1 deletion skills/objectstack-ui/rules/dashboards.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ Standardized answers to the recurring ambiguous cases:
or boolean field (`percent` not under `sum`; `min`/`max` also dates); a `formula`
has no column: refused whatever its `returnType` says. Store the number
(user-entered, a `summary` rollup, or hook-kept) or title the widget the `count`
it is. Leave days: `sum` over the `formula` `days_requested` (`end - start`) is
it is. Leave days: `sum` over the `formula` `days_requested` (`daysBetween(start, end)`) is
refused (`date` fields: no `duration`) ⇒ a stored `number` `days_count` a
`beforeInsert`/`beforeUpdate` hook writes, then `{ name: 'days_sum', aggregate: 'sum', field: 'days_count' }`.
- **Breakdown by a multi-value field.** `multiselect`/`checkboxes`/`tags` and
Expand Down
Loading