Repository navigation
Commit f9e871a
skills(objectstack-query): teach the now-anchored comparand { $now: true, addDays } (#26407)
<!-- claude-projects-attribution: {"github_login":"os-warren"} -->
_Requested by **Warren** · [project
thread](https://claude.ai/code/project/chan_01U5RdnAAmkSQmZbqh6FtqZ8?thread=cmsg_01U5RdnAAmkSQmZbqh6FtqZ8DnAiPBrJBxA6FJG4ChPE3h)_
Fixes #26301
Clause-②: no
Metadata-protocol: none
Skills-docs: updated here
## 维护者速读
- **改了什么**:`skills/objectstack-query/rules/filters.md` 在 Field
References 之后新增一节「Now-Anchored Comparands」:`{ $now: true, addDays:
OFFSET }` 的形状、唯一示例(按每条记录自己的宽限天数判逾期,偏移列存负数天)、哪里接受(六个标量比较、date/datetime
字段)、哪些被拒(无 `addDays`、小数或字串偏移、多余键、非日期目标、`$in`/`$nin`/`$between` 位置,SQL
下推拒点路径或非数值偏移列),并点明不必用存储公式字段加钩子。`SKILL.md` 的规则清单与「Cross-field
comparisons」各加一句指向它(在余量内,不抬上限)。eval 文件加第 6 题。两行 token 上限按实测抬高,裁决原文写在行旁。
- **为什么改**:该比较数自 18.0 起已在过滤语法里,但技能只分别教了 `$field` 与日期宏;AI
作者要「按每条记录自己的宽限天数判逾期」时会去建存储公式字段再写钩子。
- **风险与回滚**:只改发布技能包的文字与一个 eval 夹具,不改运行时;回滚即 revert 本 PR(两行上限随之回落)。
- **先看**:① `skills/objectstack-query/rules/filters.md` 新节(接受/拒绝清单对照
`packages/spec/src/data/filter.zod.ts` 的 `NowAnchorSchema` 与
`filter.test.ts` 的 #25808 用例);② `scripts/check-skills-token-ratchet.mjs`
两行上限及注释引的裁决;③ `evals/filters-pagination-search.json` 第 6 题。
- **两分钟验证**:`node scripts/check-skills-token-ratchet.mjs` → 末行 `81
authored bundle file(s) within their ceilings`,其中 `rules/filters.md is
2482 tokens (ceiling 2482; headroom 0)`、`filters-pagination-search.json
is 1175 tokens (ceiling 1175; headroom 0)`。
- **需要你做的**:Tier H(`skills/**`),请 approve 或直接 Merge when ready。
## What changes
Card #26301, the audit's row Q1 (the comparand `{ $now: true, addDays:
FIELD }`, in the filter grammar since the 18.0 floor, taught nowhere in
`skills/objectstack-query/**`):
1. **`rules/filters.md` — one subsection, "Now-Anchored Comparands"**,
placed directly under Field References and before Relation Filters. Why
there and not just before Date Filtering Patterns: the anchor is legal
exactly where a `{ $field }` reference is and borrows its `addDays`
grammar, so the author who has just read how a comparand can be another
column meets its now-anchored twin on the next screen; the date-token
section teaches placeholders that take a literal N only. The section
carries, in the spec's words (`NowAnchorSchema`, `filter.zod.ts`, and
the #25808 cases in `filter.test.ts` / `engine-now-anchor.test.ts`): the
shape; the one example (overdue by each record's own grace period,
`due_at < now + grace_days_offset`, the offset column holding NEGATIVE
days); that `addDays` is required and always added (an integer of any
sign, `-30` keeping the time of day where `{30_days_ago}` names a
calendar day; `{now}` for the bare instant; or a `{ $field }` naming a
number field of the same record, NULL counting as zero days); that the
filter cannot negate a positive column, so the signed offset is what
gets stored; what is refused with `INVALID_FILTER` / 400 (no `addDays`,
a fractional or string offset, an extra key — there is no `subDays` — a
target that is not `date` / `datetime`, any `$in` / `$nin` / `$between`
position; SQL push-down also refuses a dotted or non-number offset
field); and that the filter alone answers the question, not a stored
formula field plus a hook (the card's "who pays").
2. **`SKILL.md` — two pointers**, inside the file's headroom (priced
4124 → 4168, ceiling 5547): the rules list names "now-anchored
comparands" among what `filters.md` covers, and the Cross-field
comparisons entry gains one clause naming the anchor beside `{ $field
}`, pointing at the new section. Line 1 (the version stamp PR #25640
owns) is untouched.
3. **Eval 6** in `evals/filters-pagination-search.json`, in the file's
existing shape (the earlier five entries are byte-identical): the
overdue-by-grace-days prompt, with the schema stated (a `datetime` and a
number field holding negative days); expected output is the anchor as
the whole comparand of `$lt`, and names the three wrong routes (a stored
formula field plus a hook, `{N_days_ago}`, a `{ $field: 'due_at',
addDays }` column-to-column comparison). `must_contain` pins `$now:
true`, `addDays`, `$field: 'grace_days_offset'`, `$lt`;
`must_not_contain` pins `subDays` and `type: 'formula'`.
4. **`references/_index.md`** is generator-owned (`gen:skill-refs`) and
already lists `data/filter.zod.ts`; not touched.
**Token-ratchet rows** (`scripts/check-skills-token-ratchet.mjs`), per
the maintainer's ruling on the seat's decision card 「skills 修复卡的教学和 eval
都撞 token 天花板,怎么付?」, tap **A** (2026-10-27 01:21Z), verbatim and
untranslated: 「A:先削重复,余额按实测提高」. Step one, cut duplicates first: measured
on `origin/main` `a66c9ff4`, `rules/filters.md` carries no passage that
restates another in the file (the operator table's `case-blind` cells
and the Case-sensitivity paragraph are a table row and its rule, not a
restatement; the Relation Filters limits and the two-step route are
stated once each; `$field`'s comparison-position rule appears once here
and once in `SKILL.md`, a different file), and the eval file repeats
nothing — so there was nothing to cut and no surviving-home list to
give. Step two, raise by the measured remainder: `rules/filters.md` 2149
→ 2482 (+334 landed on 2148, 1 absorbed by headroom, ceiling +333);
`evals/filters-pagination-search.json` 955 → 1175 (+220, zero headroom).
Both rows re-pinned at zero headroom; the comments beside them quote the
ruling.
**Two readings** (lines, and tokens as the ratchet counts them,
`ceil(utf8 bytes / 4)`; the `SKILL.md` token figures here are the raw
file, the ratchet's priced count excludes the version line):
| File | Lines before → after | Tokens before → after |
|:--|:--|:--|
| `rules/filters.md` | 185 → 211 (+26) | 2148 → 2482 (+334) |
| `SKILL.md` | 401 → 403 (+2) | 4129 → 4173 (+44) |
| `evals/filters-pagination-search.json` (not .md, reported for the
raise) | 55 → 65 (+10) | 955 → 1175 (+220) |
| **Whole package** (`SKILL.md` + `references/**/*.md` +
`rules/**/*.md`, 5 files) | **1063 → 1091 (+28)** | **9660 → 10038
(+378)** |
Nothing removed: the card names no stale line in this package.
Sequencing (zero verbs beside these numbers): PR #25640 owns line 1 of
every `skills/*/SKILL.md`, untouched here; the sibling audit PRs #26385,
#26388, #26390, #26392 and #26393 edit other packages' rows of the
ratchet script, and this branch was cut from `a66c9ff4`, after #26392
landed; no open PR touches `skills/objectstack-query/**`.
`skip-changeset`: the published skills packages carry no `package.json`;
nothing is released from any package.
## Tests
- `node scripts/check-skills-token-ratchet.mjs` — exit 0: `81 authored
bundle file(s) within their ceilings`, the two raised rows at headroom
0, `SKILL.md` at headroom 1379 (no raise).
- `node scripts/check-skills-token-ratchet.mjs --self-test` — exit 0,
`79 cases pass`.
- `pnpm --filter @objectstack/spec run check:skill-docs`,
`check:skill-refs`, `check:skill-examples` — each exit 0
(`check:skill-examples` after building `@objectstack/spec`,
`@objectstack/client` and `@objectstack/client-react` under
`scripts/pm/os-verify-lock.sh`, slot `issue-26301`, 312 s for the whole
closure including `@objectstack/formula` and `@objectstack/lint`); the
new code block in `filters.md` is a `where:` fragment and is
deliberately not marked `os:check`, like every other fragment in the
file.
- Eval 6 is a fixture (the repo has no eval runner): its assertions were
checked by script against its expected output — every `must_contain`
string occurs in it, neither `must_not_contain` string does.
- Prose cross-checked against the contract, not paraphrased from memory:
`NowAnchorSchema` (`packages/spec/src/data/filter.zod.ts`), its #25808
cases in `filter.test.ts` (the six operators accept it; `$in` / `$nin` /
`$between` refuse it; missing, fractional and string offsets and an
extra key refuse), `engine-now-anchor.test.ts` (the resolver pins
`$now`; a numeric target and a text or undeclared offset column refuse)
and the `$now` section of
`content/docs/protocol/objectql/query-syntax.mdx` (NULL offset = zero
days; a dotted offset is refused on SQL push-down). No ablation: no
tested behaviour changed.
## Acceptance notes
- `addDays` on a `{ $field }` reference (`{ $field: 'due_date', addDays:
{ $field: 'grace_days' } }`, in the grammar since 18.0's floor as well)
is likewise untaught in Field References; the audit's row Q1 cites that
section as teaching `$field` alone and names only the anchor.
Observation only, outside this card's "Wanted"; not filed (read-only
inference, no public-door probe).
- The dispatch order's "`skills/**` inside the package's ceilings"
cannot hold at the measured headroom (`filters.md` 1, eval 0); the
claim's file surface already names the two ratchet rows under the ruling
above.
- The PR body's first two lines are the platform's attribution block
(the os-dev exception for a harness-written attribution comment); `Fixes
#26301` follows immediately. `Skills-docs: updated here` is written
because it is true, not because it is owed (`Metadata-protocol: none`).
## Verification
Gate list derived with `node scripts/pm/dispatch-gates.mjs --commands
--repo objectstack-ai/objectstack` on the final tree (`a66c9ff4` base,
head `13e75c8c`), each run locally with the exit code captured before
any pipe; `--ran` reconciliation below.
**Reconciliation** (`node scripts/pm/dispatch-gates.mjs --ran ran.list
--repo objectstack-ai/objectstack`, exit 0): `52 derived, 51 run, 1
NOT-MEASURED, 0 UNRUN`. The one NOT MEASURED is `pnpm
check:dual-build-cjs-loads`: its prerequisite is a whole-repo build (28
packages without `dist/` on this worktree after the targeted closure);
this diff touches no package source, and CI's `Lint & Repo Gates` runs
it — a declared narrowing. `check:pm-dispatch-gates` ran detached on the
final head: under six minutes of wall (launched after the 51-gate pass
ended at 02:32Z, finished 02:38Z), 1901 cases pass (fast tier; 7 slow
sections deferred), exit 0. No gate was red on any head.
<details><summary>Every derived command with its exit code on 13e75c8
(54 lines: the 52 derived, plus `check:skill-refs` and
`check:skill-examples` the card asks for)</summary>
- `node scripts/check-ci-filter-parity.mjs` — exit 0
- `node scripts/check-closing-keyword-parity.mjs` — exit 0
- `node scripts/check-closing-keyword-parity.mjs --self-test` — exit 0
- `node scripts/check-comment-mask-corpus.mjs` — exit 0
- `node scripts/check-declaration-mirrors.mjs` — exit 0
- `node scripts/check-declaration-mirrors.mjs --self-test` — exit 0
- `node scripts/check-doc-route-spelling.mjs --advisory` — exit 0
- `node scripts/check-doc-route-spelling.mjs --self-test` — exit 0
- `node scripts/check-dts-emitted.mjs --self-test` — exit 0
- `node scripts/check-scripts-symbol-anchors.mjs` — exit 0
- `node scripts/check-scripts-symbol-anchors.mjs --self-test` — exit 0
- `node scripts/check-self-test-wired.mjs` — exit 0
- `node scripts/check-self-test-wired.mjs --self-test` — exit 0
- `node scripts/check-self-test-workflow-commands.mjs` — exit 0
- `node scripts/check-self-test-workflow-commands.mjs --self-test` —
exit 0
- `node scripts/check-skills-token-ratchet.mjs` — exit 0
- `node scripts/check-skills-token-ratchet.mjs --self-test` — exit 0
- `node scripts/check-whole-set-label-write.mjs` — exit 0
- `node scripts/check-whole-set-label-write.mjs --self-test` — exit 0
- `node scripts/ci/assert-turbo-remote-cache.mjs --self-test` — exit 0
- `node scripts/ci/prune-turbo-cache.mjs --self-test` — exit 0
- `node scripts/pm/bare-root-worklist.mjs --self-test` — exit 0
- `pnpm --filter @objectstack/lint run check:doc-formula-expressions` —
exit 0
- `pnpm --filter @objectstack/spec run check:skill-docs` — exit 0
- `pnpm check:agent-test-spelling` — exit 0
- `pnpm check:bash32-floor` — exit 0
- `pnpm check:cli-command-ids` — exit 0
- `pnpm check:corpus-claim-drift` — exit 0
- `pnpm check:cross-package-test-inputs` — exit 0
- `pnpm check:doc-authoring` — exit 0
- `pnpm check:driver-memory-census` — exit 0
- `pnpm check:dts-closure` — exit 0
- `pnpm check:dual-build-cjs-loads` — NOT MEASURED (exit 3, PREREQUISITE
NOT MET)
- `pnpm check:entry-guard` — exit 0
- `pnpm check:gitlink-declared` — exit 0
- `pnpm check:lean-entry-closure` — exit 0
- `pnpm check:merge-driver` — exit 0
- `pnpm check:nul-bytes` — exit 0
- `pnpm check:parse-guard` — exit 0
- `pnpm check:pm-governed-merges` — exit 0
- `pnpm check:pm-skill-id-lint` — exit 0
- `pnpm check:pnpm-filter-targets` — exit 0
- `pnpm check:pnpm-flag-spellings` — exit 0
- `pnpm check:ratchet-remedy-authority` — exit 0
- `pnpm check:refd-timer-probe` — exit 0
- `pnpm check:role-word` — exit 0
- `pnpm check:skill-compatibility` — exit 0
- `pnpm check:skill-frame-sync` — exit 0
- `pnpm check:skill-identifier-liveness` — exit 0
- `pnpm check:sourcemap-no-sources-content` — exit 0
- `pnpm check:watch-hint-literal` — exit 0
- `pnpm check:pm-dispatch-gates` — exit 0
- `pnpm --filter @objectstack/spec run check:skill-refs` — exit 0
- `pnpm --filter @objectstack/spec run check:skill-examples` — exit 0
</details>
<details><summary>Token ratchet on 13e75c8</summary>
- `skills/objectstack-query/SKILL.md is 4168 tokens (ceiling 5547;
headroom 1379). Its release-stamp line (version: "18.1.0", 20 bytes) is
written by the version script, not an author, and is not priced.`
- `skills/objectstack-query/evals/filters-pagination-search.json is 1175
tokens (ceiling 1175; headroom 0).`
- `skills/objectstack-query/rules/filters.md is 2482 tokens (ceiling
2482; headroom 0).`
- `81 authored bundle file(s) within their ceilings; 10 generator-owned
file(s) measured, not ratcheted.`
- `--self-test`: `79 cases pass.`
</details>
<details><summary>check:skill-examples on 13e75c8</summary>
`264 prose examples type-check across 3 surface(s) — every marked block
parsed, so tsc ran the SEMANTIC pass on all of them`
</details>
---
_Generated by [Claude
Code](https://claude.ai/code/session_01JQwxbX21W6nMhxhqhmbFEH)_
Co-authored-by: objectstack-fleet[bot] <noreply@anthropic.com>1 parent 775a000 commit f9e871a
4 files changed
Lines changed: 58 additions & 4 deletions
File tree
- scripts
- skills/objectstack-query
- evals
- rules
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
628 | 628 | | |
629 | 629 | | |
630 | 630 | | |
631 | | - | |
| 631 | + | |
| 632 | + | |
| 633 | + | |
| 634 | + | |
| 635 | + | |
| 636 | + | |
| 637 | + | |
| 638 | + | |
| 639 | + | |
| 640 | + | |
| 641 | + | |
632 | 642 | | |
633 | 643 | | |
634 | 644 | | |
| |||
803 | 813 | | |
804 | 814 | | |
805 | 815 | | |
806 | | - | |
| 816 | + | |
| 817 | + | |
| 818 | + | |
| 819 | + | |
| 820 | + | |
| 821 | + | |
| 822 | + | |
807 | 823 | | |
808 | 824 | | |
809 | 825 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
86 | 86 | | |
87 | 87 | | |
88 | 88 | | |
89 | | - | |
| 89 | + | |
90 | 90 | | |
91 | 91 | | |
92 | 92 | | |
| |||
195 | 195 | | |
196 | 196 | | |
197 | 197 | | |
198 | | - | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
199 | 201 | | |
200 | 202 | | |
201 | 203 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
50 | 50 | | |
51 | 51 | | |
52 | 52 | | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
53 | 63 | | |
54 | 64 | | |
55 | 65 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
54 | 54 | | |
55 | 55 | | |
56 | 56 | | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
57 | 83 | | |
58 | 84 | | |
59 | 85 | | |
| |||
0 commit comments