You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(spec,core): recognise a filter placeholder by intent — any brace-wrapped value refuses loudly (#5586)
Recognition used the token-NAME grammar, so a placeholder carrying a
non-word character ({TODAY()}, {current-user-id}, {30 days ago},
{user.id}) classified as 'not a placeholder' and reached the driver to be
compared as a literal string — the silent-wrong-rows mode the diagnostic
exists to abolish.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MwoubC3jL271FYt9rGXwxb
fix(spec,core): a filter placeholder is recognised by INTENT — `{TODAY()}` refuses loudly instead of comparing as a literal (#5586)
8
+
9
+
`UnknownFilterTokenError` had a hole exactly where authors fall in. Recognition
10
+
used the token-NAME grammar `/^\$?\{([a-zA-Z0-9_]+)\}$/`, so any placeholder
11
+
carrying a **non-word character** classified as "not a placeholder at all" and
12
+
was handed to the driver verbatim, to be compared as a literal string — the
13
+
silent-wrong-result failure the diagnostic exists to abolish.
14
+
15
+
The failure was inverted against the author. Measured on 17.0.0-rc.2 against a
16
+
four-row fixture:
17
+
18
+
| filter value | before ||
19
+
|---|---|---|
20
+
|`due_date < '{today}'`| 2 rows | correct — the two overdue rows |
21
+
|`due_date < '{TODAY}'`| throws `UnknownFilterTokenError`| diagnostic working |
22
+
|`due_date < '{TODAY()}'`|**4 rows**| diagnostic bypassed — literal string compare, and `'2026-…' < '{'` in lexicographic order swallowed a row due a week later |
23
+
24
+
So misspelling `{today}` as `{TODAY}` was reported by name, while misspelling it
25
+
as `{TODAY()}` returned the wrong rows in silence — and the parenthesised,
26
+
kebab-case, natural-language and dotted spellings (`{TODAY()}`,
27
+
`{current-user-id}`, `{30 days ago}`, `{user.id}`) are precisely what an author
28
+
migrating from another system's macro syntax writes first.
29
+
30
+
**Both directions of the behaviour change:**
31
+
32
+
-**Previously silent, now refuses loudly** — a filter value that is entirely
33
+
brace-wrapped and outside the vocabulary now throws `UnknownFilterTokenError`
34
+
(`code: FILTER_TOKEN_UNKNOWN`, `status: 400`) on the ObjectQL read and write
35
+
paths and the analytics dataset executor, and is reported as
36
+
`filter-token-unknown` by `objectstack build` / `validate` / `lint`. Before,
37
+
it reached the data engine and compared as text.
38
+
-**Unchanged** — `{today}` / `{current_user_id}` still resolve; `{TODAY}` still
39
+
refuses with the same identity; a value that merely *contains* braces
40
+
(`'acme {x} deal'`), or is not ONE pair around the whole value (`{a}{b}`,
41
+
`{{x}}`, `{}`), is still an ordinary literal and still reaches the driver
42
+
untouched.
43
+
44
+
Recognition and vocabulary are now two named grammars rather than one:
45
+
`FILTER_TOKEN_WRAPPED_RE` (`/^\$?\{([^{}]+)\}$/`) answers "did the author mean a
46
+
placeholder", and `isContextToken` / `isDateMacroToken` answer "is it in the
47
+
vocabulary". Wide in, strict out. No escape hatch for a literal `{…}` comparand
48
+
ships with this: a repo-wide measurement across structured metadata, examples,
49
+
seed data and fixtures found zero legitimate consumers comparing a
50
+
brace-wrapped literal, and an escape syntax is a public micro-contract that can
51
+
be added the day one shows up.
52
+
53
+
Flow templates are unaffected. `interpolateFilter` in
54
+
`@objectstack/service-automation` already recognised the same wide shape and
55
+
resolves `{record.id}` / `{TODAY() + 30}` from flow variables **before** the
56
+
filter reaches ObjectQL; its hand-off to the engine is keyed on the token
57
+
vocabulary (`isKnownFilterToken`), which this change does not touch.
0 commit comments