Commit eed2dee
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
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
79 | 79 | | |
80 | 80 | | |
81 | 81 | | |
82 | | - | |
83 | | - | |
84 | | - | |
85 | | - | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
86 | 86 | | |
87 | 87 | | |
88 | 88 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
318 | 318 | | |
319 | 319 | | |
320 | 320 | | |
321 | | - | |
| 321 | + | |
322 | 322 | | |
323 | 323 | | |
324 | 324 | | |
325 | 325 | | |
326 | | - | |
327 | | - | |
328 | | - | |
| 326 | + | |
| 327 | + | |
| 328 | + | |
329 | 329 | | |
330 | 330 | | |
331 | 331 | | |
| |||
0 commit comments