Skip to content

Commit eed2dee

Browse files
docs(skills): date-bucket engine sentences name what each arm emits (#21677)
Fixes #21588 Clause-②: no Three sentences in two published skills describe the date-bucket engine, and each one named something the runtime does not do. This PR rewrites exactly those three sentences, read arm by arm from the driver code on `origin/main` at `55e6f14f8d`, and nothing else in either file. | File | Line | Before | After | |:--|:--|:--|:--| | `skills/objectstack-ui/rules/dashboards.md` | 321 | `'week'` row: "ISO date of the bucket (`YYYY-MM-DD`)" | "ISO week `YYYY-Www`" | | `skills/objectstack-ui/rules/dashboards.md` | 326–328 | "Postgres `date_trunc`, MySQL `date_format`, SQLite `strftime`, MongoDB `$dateTrunc`, in-memory fallback. All emitted by the analytics service, not the client." | "drivers emit the bucket as a label (`2026-01`), not an instant: Postgres `to_char`, MySQL `date_format`, SQLite `strftime`, MongoDB `$dateToString`, in-memory `bucketDateKey`." | | `skills/objectstack-query/rules/aggregation.md` | 82–85 | "pushes bucketing down to the driver (`DATE_TRUNC` etc.)" | "pushes bucketing down to the driver (`to_char` / `date_format` / `strftime` / `$dateToString`, never `date_trunc`)" — the push-down / in-memory-fallback clause and "**including the column keys**" are kept as they were | The ruling on the card (triage comment 5969875145) fixed the engine sentence and said no other sentence in `dashboards.md` moves; the engine seat's carrier addition (5973137901) measured the `'week'` row and the aggregation sentence as the same family, and the dispatching seat folded all three into this one governed PR (claim comment 5975736098). ## Reading 1 — what each arm emits, read from the code Every arm answers a string label, never a truncated instant; the week label is the ISO week `YYYY-Www` on all five. | Arm | Where | Expression emitted | Example keys | |:--|:--|:--|:--| | PostgreSQL | `packages/drivers/driver-sql/src/sql-driver.ts:6161–6169` (`buildDateBucketExpr`) | `to_char((col)::timestamptz AT TIME ZONE 'UTC', FORMAT)` for a datetime column, `to_char((col)::date::timestamp, FORMAT)` for a `Field.date`; formats `YYYY`, `YYYY-MM`, `YYYY-MM-DD`, `YYYY"-Q"Q`, `IYYY"-W"IW` | `2026-01`, `2026-Q1`, `2026-W23` | | MySQL | `sql-driver.ts:6172–6180` | `date_format(convert_tz(col, @@session.time_zone, '+00:00'), FORMAT)` (bare `col` for a `Field.date`); `%Y`, `%Y-%m`, `%Y-%m-%d`, `%x-W%v`; quarter is `concat(date_format(…, '%Y'), '-Q', quarter(…))` | `2026-01`, `2026-W23` | | SQLite | `sql-driver.ts:6183–6208` | `strftime(FORMAT, ARG)` with `%Y`, `%Y-%m`, `%Y-%m-%d`; quarter from `%Y` and `(%m - 1) / 3 + 1`; week by the Thursday rule, `strftime('%Y', ARG, '-3 days', 'weekday 4') \|\| '-W' \|\| printf('%02d', (cast(strftime('%j', ARG, '-3 days', 'weekday 4') as integer) - 1) / 7 + 1)` (PR #21629, merged, on `origin/main`) | `2026-01`, `2026-W23` | | MongoDB | `packages/drivers/driver-mongodb/src/mongodb-aggregation.ts:246–275` | `{ $dateToString: { format, date: { $convert: { input: '$FIELD', to: 'date', onError: null, onNull: null } } } }` with `%Y`, `%Y-%m`, `%Y-%m-%d`, `%G-W%V`; quarter is `$concat` of `%Y`, `-Q` and a `$switch` over `%m`. The docblock at `:194` is headed "Labels, not instants — and therefore no `$dateTrunc`" | `2026-01`, `2026-W23` | | In-memory | `packages/core/src/utils/datetime.ts:313` `bucketDateKey` (week via `isoWeekLabelFromCalendarDay`, `:389`); the engine's fallback `packages/objectql/src/in-memory-aggregation.ts:375` delegates to it, and `packages/objectql/src/engine.ts:17615` picks push-down vs fallback from `supports.queryDateGranularity` | string keys `YEAR`, `YEAR-MM`, `YEAR-MM-DD`, `YEAR-Qn`, `ISOYEAR-Www` built from the calendar parts in the reference zone — the writer the drivers' expressions are held equal to (`checkDateBucketParity`) | `2026-01`, `2026-W23` | The rendered dashboard label is the key: `packages/services/service-analytics/src/dimension-labels.ts:321–333` `formatDateBucket` returns a key the writer wrote at that granularity as written (`bucketKeyToCalendarRange(value, granularity) !== null`), and `src/__tests__/dataset-granularity-postprocess.test.ts:66` pins `week: '2026-W29'` through `queryDataset`. So the `'week'` row's "ISO date of the bucket (`YYYY-MM-DD`)" was wrong on every face, and the engine sentence named two expressions no arm emits. ## Reading 2 — token ratchet and line count, before / after `node scripts/check-skills-token-ratchet.mjs` (ceil(utf8 bytes / 4)), measured on the worktree before the edit and at `e381bcd9e1` after it: | File | Tokens before | Tokens after | Bytes | Lines | |:--|:--|:--|:--|:--| | `skills/objectstack-ui/rules/dashboards.md` | `6243 tokens (ceiling 6252; headroom 9)` | `6243 tokens (ceiling 6252; headroom 9)` | 24970 → 24969 | 468 → 468 | | `skills/objectstack-query/rules/aggregation.md` | `1845 tokens (ceiling 2357; headroom 512)` | `1860 tokens (ceiling 2357; headroom 497)` | 7378 → 7437 | 241 → 241 | Per sentence: the `'week'` row 52 → 34 bytes; the engine sentence 185 → 202 bytes over the same three lines; the aggregation sentence 244 → 303 bytes over the same four lines. Net for `dashboards.md`: 0 tokens, 0 lines, −1 byte. No ceiling moved. ## Gates All runs at `e381bcd9e1` (the branch's only commit); each runner log records the sha it started at. - `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derives 23 commands from the change set (2 paths vs merge base `55e6f14f8`). All 23 exit 0. The 8 `node scripts/check-*.mjs` runs plus `check:doc-formula-expressions`, `check:agent-test-spelling` and `check:corpus-claim-drift` ran under `scripts/pm/os-verify-lock.sh` (VERDICT command-exit 0, held 120s). The remaining 12 `pnpm check:*` scanners ran unlocked — a declared narrowing: the lock's own `--status` text places `check:*` gate scripts outside its coverage, and two consecutive lock calls answered queue-timeout (exit 99, 360s each) behind a holder at 13+ minutes. - `check:doc-formula-expressions` first answered exit 3 PREREQUISITE NOT MET (`@objectstack/formula` / `@objectstack/lint` not built — nothing measured). The production closure (`spec`, `types`, `core`, `client`, `client-react`, `formula`, `sdui-parser`, `lint`) was built under the lock (VERDICT command-exit 0, held 133s; `git status` clean afterwards), and the re-run exits 0: "22 record-scoped formula example(s) across 460 files / 1381 TS blocks judged clean by @objectstack/formula". - `pnpm --filter @objectstack/spec run check:skill-docs` exits 0: "✅ Skill docs in sync". - `pnpm --filter @objectstack/spec run check:skill-examples` exits 0 (run unlocked after a third queue-timeout, same declared narrowing): "✅ 260 prose examples type-check across 3 surface(s) — every marked block parsed, so tsc ran the SEMANTIC pass on all of them". The diff sits outside every fence (dashboards.md fences close at 316 and reopen at 354; aggregation.md's close at 72 and reopen at 95), so no example changed. - `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran` over the 23 commands with their exit codes: "23 derived famil(ies) accounted for — 23 run, 0 NOT-MEASURED (a DERIVED zero — all 23 recorded an exit code and none of them is 3)". The tool warns the tree is 3 commits behind `origin/main` (`417443eb27`, fetched after the runs); re-deriving against that merge-base yields the same 23 commands, and the three incoming commits (#21662, #21653, #21661) touch no `skills/**` path, so the branch was not merged forward for a two-file documentation change. - `pnpm lint` narrowing, with the three pieces of evidence: ① `eslint.config.mjs` `files:` globs cover only `{ts,tsx,mts,cts,js,jsx,mjs,cjs}` (lines 971–1238), so neither `.md` file is in the population; ② `pnpm exec eslint --no-inline-config --format json` over the two files answers 2 files, 0 errors, 1 warning each — "File ignored because no matching configuration was supplied."; ③ the config enables no `parserOptions.project` or typed rules (line 328), so this diff moves no untouched file's verdict. - No package is touched, so no build closure (①) and no package test / typecheck (②) is owed; `check:nul-bytes` is among the 23. ## 维护者速读(草稿) **改了什么** — 两个已发布技能里描述日期分桶引擎的三句话。dashboards 规则的「Engine support」句原说 Postgres 用 `date_trunc`、MongoDB 用 `$dateTrunc`、由 analytics service 发出;改为按五个臂点名驱动真实发出的表达式(Postgres `to_char`、MySQL `date_format`、SQLite `strftime`、MongoDB `$dateToString`、内存 `bucketDateKey`),并写明桶键是标签(如 `2026-01`)不是时刻。同一张表的 `'week'` 行由「ISO date of the bucket (`YYYY-MM-DD`)」改为 ISO 周标签 `YYYY-Www`。aggregation 规则的下推句把 `DATE_TRUNC` 换成同一表达式族。不改任何代码,不改产品行为。 **为什么改** — 技能是 AI 作者读的权威面。写错引擎会让作者按 `date_trunc` 语义(时间戳形的桶键)去比较或解析桶值,而运行时五个臂实际都返回字符串标签;`'week'` 行与运行时每个臂返回的 `2026-W29` 不符。每个臂都在 `origin/main`(`55e6f14f8d`)上逐条读过代码,见上文 Reading 1。 **风险与代价(含回滚)** — 纯文档改动,8 行替换 8 行。token 棘轮:dashboards.md 净 0(6243/6252 不变,行数不变),aggregation.md +15(1860/2357)。23 条派生门禁加 `check:skill-docs`、`check:skill-examples` 全绿。回滚 = revert 本 PR 的单个 commit。 **席位意见** — (留空) **你要做的** — 对这个 Tier H `skills/**` PR 给一次 APPROVED review;之后由 `domain:skills#1` 席位落地。 ## Acceptance notes Noted, not filed (code comments, no behaviour, no carrier): - `packages/services/service-analytics/src/dimension-labels.ts:302` — the `formatDateBucket` TSDoc example list still reads `week → "2026-04-13" (ISO date of the bucket)`, while the body just below (`:327–:333`) returns a `YYYY-Www` key as written and relabels only a raw non-key value as its own day key. Comment drift only; the behaviour is pinned by the tests cited above. - `packages/objectql/src/in-memory-aggregation.ts:59` — the header comment names `date_trunc(...)` as the SQL path's NULL-propagating expression; the SQL path emits `to_char` / `date_format` / `strftime`, whose NULL propagation is the same point. Comment drift only. --- _Generated by [Claude Code](https://claude.ai/code/session_01CB6W87z22K2yjUCDyVrJRk)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent 93a54b8 commit eed2dee

2 files changed

Lines changed: 8 additions & 8 deletions

File tree

‎skills/objectstack-query/rules/aggregation.md‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,10 @@ aggregations (never bucket by hand in app code):
7979
the projected COLUMN only** — grouping still keys on the field, so the buckets
8080
themselves are unchanged. Read the result under `alias ?? field`, and reference
8181
that same name from `having`.
82-
- The engine pushes bucketing down to the driver (`DATE_TRUNC` etc.) when
83-
the dialect supports that granularity, and transparently falls back to
84-
in-memory bucketing otherwise — results are correct either way, **including
85-
the column keys**.
82+
- The engine pushes bucketing down to the driver (`to_char` / `date_format` /
83+
`strftime` / `$dateToString`, never `date_trunc`) when the dialect supports
84+
that granularity, and transparently falls back to in-memory bucketing
85+
otherwise — results are correct either way, **including the column keys**.
8686

8787
## HAVING Clause
8888

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -318,14 +318,14 @@ const signedByMonth: DashboardWidget = { id: 'signed_by_month', type: 'line',
318318
| `dateGranularity` | Rendered bucket label |
319319
|:--|:--|
320320
| `'day'` | `YYYY-MM-DD` |
321-
| `'week'` | ISO date of the bucket (`YYYY-MM-DD`) |
321+
| `'week'` | ISO week `YYYY-Www` |
322322
| `'month'` | `YYYY-MM` |
323323
| `'quarter'` | `YYYY-Qn` |
324324
| `'year'` | `YYYY` |
325325

326-
* **Engine support** — Postgres `date_trunc`, MySQL `date_format`, SQLite
327-
`strftime`, MongoDB `$dateTrunc`, in-memory fallback. All emitted by the
328-
analytics service, not the client.
326+
* **Engine support** — drivers emit the bucket as a label (`2026-01`), not
327+
an instant: Postgres `to_char`, MySQL `date_format`, SQLite `strftime`,
328+
MongoDB `$dateToString`, in-memory `bucketDateKey`.
329329
* **Human labels are automatic** — the analytics layer formats the bucket value
330330
to the label above, and resolves `select`/`lookup` dimension values to their
331331
option label / related-record name. Measures carry their `label` + `format`

0 commit comments

Comments
 (0)