Repository navigation
Commit c68869c
fix(spec)!: every filter door refuses a $now anchor that carries a key beyond $now / addDays (#26592)
<!-- claude-projects-attribution: {"github_login":"os-support-ai"} -->
_Requested by **Yibo Wang** · [project
thread](https://claude.ai/code/project/chan_01BVUyyPie8gHwoPM18iABwB?thread=cmsg_01BVUyyPie8gHwoPM18iABwB4zZywfLfQ2M3x31mQ5qLJm)_
Fixes #26413
Clause-②: no (narrowing) — every filter door refuses a `$now` anchor
carrying a key beyond `$now` / `addDays`, which it ran before. Nothing
is widened.
Metadata-protocol: narrowed (a filter saved through
`FilterConditionSchema` may no longer carry a key beside `$now` and
`addDays` on a now-anchored comparand)
Skills-docs: none — `skills/objectstack-query/rules/filters.md` (the
refusal list under "Now-Anchored Comparands") already teaches that "any
extra key (there is no `subDays`)" is refused with `INVALID_FILTER` /
400, and eval `filters-pagination-search.json` already forbids
`subDays`; this diff makes that sentence true at every door, so it needs
no edit.
## 维护者速读
- **改了什么:** 所有过滤入口共用的 `$now` 锚点检查(`isWellFormedNowAnchor`)现在拒收 `$now` /
`addDays` 之外的任何键,报 `INVALID_FILTER` / 400 并点名多余的键。
- **为什么改:** `NowAnchorSchema` 声明为封闭,但入口只看 `addDays`,`subDays: 7`
这类键被静默忽略,查询按 `addDays` 跑完、不报错(ADR-0049:声明即须执行)。
- **风险与回滚:** 收窄:之前带多余键能跑的查询和能保存的过滤现在被拒;合法锚点不受影响。回滚即还原
`filter-comparand-type.ts` 一处。
- **先看:**
`packages/spec/src/data/filter-comparand-type.ts`(`isWellFormedNowAnchor`
与 `malformedNowAnchorRefusal`
新分支)、`packages/objectql/src/engine-now-anchor.test.ts`(五个入口的钉)、`.changeset/26413-now-anchor-extra-key-refused.md`。
- **两分钟验证:** `pnpm --filter @objectstack/spec exec vitest run
src/data/filter-comparand-type.test.ts`,预期新的 `[#26413]` 组全绿。
- **需要你做的:** 无,席位落地。
## What changes
- `packages/spec/src/data/filter-comparand-type.ts`:
`isWellFormedNowAnchor` now answers false for an anchor with any key
other than `$now` / `addDays`, so the comparand-type walk that every
filter door runs refuses it. `malformedNowAnchorRefusal` gains an
undeclared-key arm, checked first: `it carries an undeclared key
"subDays" — the anchor takes $now and addDays only (there is no subDays:
a negative addDays subtracts)`. No new error code: the doors keep
`INVALID_FILTER` / 400. The key set is restated locally (`filter.zod.ts`
imports this module) and the test reconciles it against
`NowAnchorSchema.shape`.
- Doors this reaches, all through the one walk: `find()` / `count()` /
`update()` in object and array form, `aggregate()`'s `where`,
`aggregations[i].filter`, `having`, and `FilterConditionSchema` on save.
A well-formed anchor and its resolved form (`$now` holding the pinned
ISO instant) run as before.
- `@objectstack/spec` changeset, `minor` (see Acceptance notes on the
level).
## Tests
- `packages/spec/src/data/filter-comparand-type.test.ts`: new `[#26413]`
group: the door's key set equals `NowAnchorSchema.shape`; refusal with
code, status and the named key at all six scalar comparisons for an
integer offset, a `{ $field }` offset and the resolved form; under
`$and` / `$or` / `$not` and through both `parseFilterAST` forms; every
undeclared key named, before a malformed offset; well-formed anchors
kept by reference.
- `packages/spec/src/data/filter.test.ts`: the `FilterConditionSchema`
save door refuses `subDays` and `note`, naming the key, beside the
existing closed-schema pin; the clean anchor still parses.
- `packages/objectql/src/engine-now-anchor.test.ts`: new `[#26413]`
group, one answer at every door: `find()` object form (driver never
called), `find()` array form, `aggregate()` `where`,
`aggregations[i].filter`, `having`, each with code, status and the named
key, and each with the clean anchor still running.
- `packages/formula/src/matches-filter-now-anchor.test.ts`: **narrowed,
not retired.** Measured: it does not contradict the refusal. The engine
reaches this evaluator only through `having-filter.ts` (`having` and the
`aggregations[i].filter` rows fallback), both behind the door, and the
CEL lowering for the RLS `check` emits no anchor. The assertion stays;
the title and a comment now say the evaluator sits behind the door and
does not re-judge the keys, instead of presenting the tolerance as the
anchor's contract.
## Acceptance notes
- **Level vs. the order.** The dispatch said "a patch changeset" and
"Metadata-protocol as measured". Measured:
`FilterConditionSchema.safeParse({ opened_at: { $lt: { $now: true,
addDays: -30, subDays: 7 } } })` was `success: true` on base and is
refused now, so a value a metadata author could save is refused:
`narrowed`. AGENTS.md's level table makes a declared kind at least
`minor` on a moved package, so the changeset is `@objectstack/spec:
minor`. `objectql` and `formula` change tests only and publish nothing.
- **ADR-0087:** `not-required (no-migration-prescription)`: no key is
added, removed or renamed (`NowAnchorSchema` declared exactly two keys
since 18.2.0), and an extra key never had a meaning at any executor, so
no rewrite keeps what the author meant.
- Noted, not fixed (from the earlier report on this card, unchanged by
this diff): the bare implicit-equality spelling `{ opened_at: { $now:
true, addDays: -30, note: 'x' } }` is read by the walk as an operator
bag, not a comparand, so it is outside this refusal. `having` already
refuses the bare spelling; its answer at `where` against a real driver
was not measured.
## Verification
Readings at head `98b536ff`. Builds and tests ran under
`os-verify-lock`.
<details><summary>Package tests and typecheck</summary>
- `pnpm --filter @objectstack/spec test`: exit 0, 729 files, 22055
passed, 1 todo.
- `pnpm --filter @objectstack/formula test`: exit 0, 54 files, 1452
passed.
- `pnpm --filter @objectstack/objectql test`: exit 0, 472 files, 8794
passed (after `pnpm --filter '@objectstack/objectql^...' build`).
- `pnpm --filter @objectstack/{spec,formula,objectql} typecheck`: exit 0
each.
- `pnpm exec eslint --no-inline-config` on the five changed source and
test files: exit 0 (the repo-wide `pnpm lint` is CI's run).
</details>
<details><summary>Ablation (one, on the guard line)</summary>
Committed fix first, then `node scripts/ablation-replace.mjs` replaced
`if (undeclaredNowAnchorKeys(value).length > 0) return false;` with a
branch that never fires (marker `ABLATED_26413`). Anchor 1 to 0, blob
`a7e88059` to `64df538e`; spec rebuilt; `ablation-dist-preflight` found
the marker in 2 built files. Expected direction red; observed red: spec
3 failed / 221 passed (the three new refusal pins), objectql 5 failed /
15 passed (all five new door pins). Restore: blob equals HEAD, `git diff
HEAD` empty, spec rebuilt, preflight `--absent` clean over 580 built
files, whole-tree `git status --porcelain` empty.
</details>
<details><summary>Derived gates</summary>
`node scripts/pm/dispatch-gates.mjs --commands` derived 82 families; 80
exit 0. `check:dual-build-cjs-loads` and `check:lean-entry-closure` exit
3 (prerequisite: they read whole-repo built output; this worktree built
the objectql closure only), NOT MEASURED locally, left to CI. `--ran`
reconciliation: 82 derived, 80 run, 2 NOT-MEASURED, 0 UNRUN.
`check-adr-0087-registration --base origin/main`: one declared-breaking
changeset, disposition carried. Local-only:
`check-skill-frame-freshness` exit 0; `check-dev-prereqs` exit 1 because
the workspace is not fully built (37 packages lack `dist/`), an
environment state, not a finding. The full dogfood battery reading goes
in the os-dev report on the card.
</details>
## Human Minimum
- [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic.
- [x] 2. Every name I added uses the casing for its kind.
- [x] 3. No formatter ran; `pnpm lint` passes.
- [x] 4. One issue, one branch off an up-to-date `main`, one pull
request.
- [x] 5. A change that publishes carries a changeset, and both
declaration lines are filled in here and in the changeset.
- [x] 6. `packages/spec` touched: its generated artifacts are
regenerated and committed.
- [x] 7. No hand edits to `content/docs/references/`,
`packages/*/CHANGELOG.md` or `content/docs/releases/`.
- [x] 8. Title, body and comments are in English.
- [x] The *Before Submitting* list in CONTRIBUTING.md is done.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01WGufzf89Y8Q23nPngB94Bo)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent f986702 commit c68869c
6 files changed
Lines changed: 186 additions & 10 deletions
File tree
- .changeset
- packages
- formula/src
- objectql/src
- spec/src/data
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
164 | 164 | | |
165 | 165 | | |
166 | 166 | | |
167 | | - | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
168 | 174 | | |
169 | 175 | | |
170 | 176 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
256 | 256 | | |
257 | 257 | | |
258 | 258 | | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
| 297 | + | |
| 298 | + | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
| 306 | + | |
| 307 | + | |
| 308 | + | |
| 309 | + | |
| 310 | + | |
| 311 | + | |
| 312 | + | |
| 313 | + | |
| 314 | + | |
| 315 | + | |
| 316 | + | |
259 | 317 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
21 | 21 | | |
22 | 22 | | |
23 | 23 | | |
| 24 | + | |
24 | 25 | | |
25 | 26 | | |
26 | 27 | | |
| |||
295 | 296 | | |
296 | 297 | | |
297 | 298 | | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
| 306 | + | |
| 307 | + | |
| 308 | + | |
| 309 | + | |
| 310 | + | |
| 311 | + | |
| 312 | + | |
| 313 | + | |
| 314 | + | |
| 315 | + | |
| 316 | + | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
| 322 | + | |
| 323 | + | |
| 324 | + | |
| 325 | + | |
| 326 | + | |
| 327 | + | |
| 328 | + | |
| 329 | + | |
| 330 | + | |
| 331 | + | |
| 332 | + | |
| 333 | + | |
| 334 | + | |
| 335 | + | |
| 336 | + | |
| 337 | + | |
| 338 | + | |
| 339 | + | |
| 340 | + | |
| 341 | + | |
| 342 | + | |
| 343 | + | |
| 344 | + | |
| 345 | + | |
| 346 | + | |
| 347 | + | |
| 348 | + | |
| 349 | + | |
| 350 | + | |
| 351 | + | |
| 352 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
224 | 224 | | |
225 | 225 | | |
226 | 226 | | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
227 | 239 | | |
228 | 240 | | |
229 | | - | |
230 | | - | |
231 | | - | |
232 | | - | |
233 | | - | |
234 | | - | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
235 | 253 | | |
236 | 254 | | |
| 255 | + | |
237 | 256 | | |
238 | 257 | | |
239 | 258 | | |
240 | 259 | | |
241 | 260 | | |
242 | 261 | | |
243 | | - | |
244 | | - | |
245 | | - | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
246 | 269 | | |
247 | 270 | | |
248 | 271 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
265 | 265 | | |
266 | 266 | | |
267 | 267 | | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
268 | 280 | | |
269 | 281 | | |
270 | 282 | | |
| |||
0 commit comments