Repository navigation
Commit 85be049
fix(lint): filter-token-unknown states the runtime's refusal, FILTER_TOKEN_UNKNOWN / 400 (#24312)
Fixes #24293
Clause-②: no
## What changes
`filter-token-unknown`'s message said an unresolvable filter placeholder
"is sent to the data engine as a literal string, matches no record, and
the surface renders empty". The runtime does not do that: the data
engine refuses a query that carries one, `FILTER_TOKEN_UNKNOWN` / 400.
The message now says so:
```text
Filter value "{current_user}" is not a resolvable placeholder. The data engine refuses a query that carries it (`FILTER_TOKEN_UNKNOWN`, 400), so the surface never shows the data the filter meant.
```
Message and comment text only. Unchanged: the rule id, severity
(`error`), `path`, `hint`, and which values the rule reports. The
message's first sentence is unchanged, so the negative pin at
`validate-filter-tokens.test.ts` (the `{record_id}` refusal must not
read as an unknown spelling) still guards the same text.
## Every site of the false sentence, on base `21070a2f0a`
| site | done |
|:--|:--|
| `packages/lint/src/validate-filter-tokens.ts:236`–`:237`, the message
| rewritten as above |
| `validate-filter-tokens.ts:25`–`:26`, the module header ("passed to
the data engine **verbatim**, matches no row") | rewritten: the engine
refuses the query, naming `UnknownFilterTokenError` in
`@objectstack/core` |
| `validate-filter-tokens.ts:28`–`:41`, the header's "Why this is an
error" section ("The runtime failure mode is silent", present tense) |
put in the past tense: the silent zero is what the rule was written
against, before the runtime resolved placeholders. Says why the rule
stays an error now that the runtime refuses: the refusal arrives only
when a surface is opened, after the build passed, which an AI author's
author, validate, correct loop never sees |
| `validate-filter-tokens.ts:106`, the `severity` field doc ("silently
matches nothing") | rewritten: the engine refuses a query carrying an
unresolvable placeholder |
| `packages/lint/src/validate-dataset-references.ts:36`–`:39`, the
docblock quoting the old message (triage `6248475229`) | corrected. The
quote is gone, and the comparison it carried is inverted: the value
`filter-token-unknown` reports is refused loudly, while a dangling field
path at the same node is the quiet failure, per that file's own severity
note |
| `packages/lint/src/authoring-rules.ts:917`–`:920`, the comment above
`validateFilterTokens`' registration ("reaches the data engine as a
literal and matches nothing. The surface renders empty with no error").
The comment ships inside `@objectstack/lint`'s `dist/index.js` and
`dist/runtime.js` | rewritten in `f3d1666d9`, comment text only. The
claim's file surface was amended to add it (`6249110232`). The `#3574`
provenance and the silent-zero history are kept, in the past tense, as
in the module header. Before and after are below |
The `authoring-rules.ts` comment before (`21070a2f0`, `:917`–`:920`):
```text
// #3574 — a filter value like `{current_user}` resolves in no vocabulary,
// reaches the data engine as a literal and matches nothing. The surface
// renders empty with no error, and a silent zero is indistinguishable from a
// genuine one at review time.
```
and after (`f3d1666d9`, `:917`–`:921`):
```text
// #3574 — a filter value like `{current_user}` resolves in no vocabulary. The
// data engine refuses a query carrying it (`FILTER_TOKEN_UNKNOWN`, 400). When
// #3574 was filed it reached the engine as a literal and matched nothing: the
// surface rendered empty with no error, a silent zero indistinguishable from a
// genuine one at review time.
```
`packages/lint/src/rule-explanations.ts` (`os explain
filter-token-unknown`, from #24292) already says "the data engine
refuses the query (`FILTER_TOKEN_UNKNOWN`, 400) rather than compare the
placeholder as a literal string, so the surface never shows the data the
filter meant". The new message takes its words, so the two agree. That
file is not touched.
## What the runtime does, read on base `21070a2f0a`
- `packages/core/src/utils/filter-tokens.ts`: `resolveFilterTokens`
throws `UnknownFilterTokenError` (`status = 400`, `code =
'FILTER_TOKEN_UNKNOWN'`) for a whole-string placeholder outside both
vocabularies (`:547`), and for one the resolver drops (`:552`).
- Callers: the ObjectQL engine on every read verb (`find`, `findOne`,
`count`, `aggregate`, through `resolveWhereTokens`) and on `update` /
`delete` (`withResolvedWhere`); the analytics service on `where`,
`timeDimensions[].dateRange` and a dataset's own filter and measure
filters (`analytics-service.ts:1852`–`:1883`,
`dataset-executor.ts:128`).
- Pins:
`packages/objectql/src/engine-filter-token-today-spelling.test.ts:71`–`:74`
(`FILTER_TOKEN_UNKNOWN` / 400) and
`packages/rest/src/rest-aggregate-positions.test.ts:171`, which asserts
`_status` 400 and `code` `FILTER_TOKEN_UNKNOWN` at `POST
/api/v1/data/:object/query` for a `where` carrying `'{not_a_token}'`.
- objectui's client resolver (`packages/core/src/utils/filter-tokens.ts`
there, `resolveContextTokens`) passes an unknown token through
untouched, so it reaches the server and is refused there.
- The message claims the engine's door only, and its claim is
conditional ("refuses a query that carries it"). A surface whose filter
never becomes a server query is outside the claim. The one such door
this read found is in Acceptance notes.
## Tests
On head `f3d1666d9` (round 1's `6be194c50`, plus the
`authoring-rules.ts` comment commit):
- `pnpm --workspace-concurrency=2 --filter '@objectstack/lint...' build`
(lint and its build closure): `VERDICT command-exit 0`. The old
sentence's phrases ("reaches the data engine as a literal", "renders
empty with no error", "sent to the data engine as a literal string",
"passed to the data engine", "matches no record, and the surface renders
empty", "unresolved placeholder silently matches nothing") have 0 hits
in `packages/lint/src` and 0 in `packages/lint/dist`. The controls hit:
the new comment's "data engine refuses a query carrying it" (src 1, dist
4 files) and the new message (dist 4 files).
- `pnpm --filter @objectstack/lint exec vitest run --maxWorkers=2`:
`Test Files 144 passed (144)`, `Tests 6858 passed (6858)`. The 5 cases
that `6be194c50`'s run skipped (`6853 passed | 5 skipped`) are the
`skipIf` cases in `lazy-deps.test.ts` and `runtime-lazy-deps.test.ts`.
They need lint's own built `dist/`, which that run did not have. This
time they ran, against the rebuilt `dist/`.
- `pnpm --filter @objectstack/lint typecheck`: `VERDICT command-exit 0`.
`check:test-typecheck: OK`. `tsc -p tsconfig.test.json --listFiles`
lists `validate-filter-tokens.test.ts`, so the test layer compiles the
new case.
- New case in `validate-filter-tokens.test.ts`: "names the code the data
engine refuses the query with, near miss or not". It holds both arms of
the unknown-token finding (a near miss `{current_user}`, and
`{whoever_is_watching}` with none) to the named code
`FILTER_TOKEN_UNKNOWN`, not to the prose around it.
- No test pinned the old prose. A repo-wide `git grep` for its phrases
("literal string, matches no", "surface renders empty", "sent to the
data engine", "passed to the data engine", "is not a resolvable
placeholder") (plus "reaches the data engine as a literal") outside the
changeset, which quotes the old text as its FROM, finds only the kept
first sentence and the negative pin above. The same grep over objectui
at `32b492b67` finds nothing.
`packages/cli/test/authoring-rule-command-parity.test.ts:107` names the
rule id only.
- Ablation, one-off, through `scripts/ablation-replace.mjs` (anchor hit
x1 to x0, blob `47d8294046cf` to `a81f9a88dae0`): with ``
(`FILTER_TOKEN_UNKNOWN`, 400) `` removed from the message, `Tests 1
failed | 33 passed (34)`, failing on exactly the new case: `expected
'Filter value "{current_user}" is not …' to contain` the code. Restored:
blob equals HEAD (`47d8294046cf`), `git diff HEAD` empty, `git status`
clean. The test imports the source by relative path, so no `dist/` sits
between the mutation and the run.
## Gates (local subset; the whole farm is CI's)
Derived on head `f3d1666d9` with `node scripts/pm/dispatch-gates.mjs
--commands --repo objectstack-ai/objectstack`, no paths given (change
set: these 5 files vs merge base `21070a2f0`). That gives 64 commands,
the same set as on `6be194c50`, and 11 more than the claim's path-only
lead of 53. The extra 11 are the changeset-kind families. Each was run
with its exit code captured before any pipe, and the record was
reconciled with `--ran`: "64 derived famil(ies) accounted for — 62 run,
2 NOT-MEASURED (2 DERIVED from a recorded exit 3)", exit 0.
- 62 exited 0, `pnpm check:docs-transcript-drift` among them, since
lint's `dist/` was built this time. On `6be194c50`, 61 exited 0 on the
first pass. These include `check:nul-bytes`, `check:issue-citations`,
`check:type-check-coverage`, `check:type-check-debt`,
`check:slot-lookup`, `check:changeset-gate-self-tests`,
`check-changeset-no-major.mjs --base origin/main` and
`check-empty-changeset.mjs --base origin/main`.
- `pnpm check:docs-transcript-drift`: on `6be194c50` it exited 3 on the
first pass (PREREQUISITE NOT MET: no `packages/lint/dist`), then 0 after
a lint build. On `f3d1666d9` it exited 0: "4 declared transcript
value(s) across 417 page(s) under content/docs/ equal what the registry
derives today, and no undeclared block quotes one". The built
`dist/index.js` carries the new message, and the old sentence is gone
from it (0 hits).
- NOT MEASURED, PREREQUISITE NOT MET (exit 3): `pnpm
check:dual-build-cjs-loads` needs every package's `dist/` (a whole `pnpm
build`), and `pnpm check:lean-entry-closure` needs
`@objectstack/objectql`'s `dist/`. Both are left to CI.
- `origin/main` has moved past the base (`2ee5861b0`, `0c3db0f6a`,
`30da04f8e` and later). None of these commits touches `packages/lint`,
its build closure, or `packages/core/src/utils/filter-tokens.ts`. The
branch is not merged with them, as the patch round directs, and CI runs
on GitHub's merge ref. The derivation on `f3d1666d9` flags 4 gate inputs
that changed on `main` across that range:
`scripts/check-single-authz-resolver.mjs`,
`scripts/slot-lookup-baseline.json`, `scripts/check-route-envelope.mjs`
and one `scripts/adr-anchors/` file. Their local readings are of the
base's copies, and CI's readings on the merge ref are the ones that
count.
## Acceptance notes
- **objectui's in-memory `ValueDataSource`** (`provider: 'value'`,
`packages/core/src/adapters/ValueDataSource.ts` at objectui `32b492b67`)
evaluates a filter on the client and sends no query, so no engine
refuses there. Its matcher has no placeholder handling, so a literal
`{…}` compares as a string. This is read from code, NOT MEASURED. The
new message does not claim that door, because its claim is conditional
on a query reaching the engine.
- The REST refusal is cited from the existing pin
(`rest-aggregate-positions.test.ts:171`), not re-run here. CI's `Test
Core` runs it.
---
_Generated by [Claude
Code](https://claude.ai/code/session_019oFY6Hi35J7atvgVFnmoaK)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 2ce2e36 commit 85be049
5 files changed
Lines changed: 57 additions & 22 deletions
File tree
- .changeset
- packages/lint/src
| 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 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
914 | 914 | | |
915 | 915 | | |
916 | 916 | | |
917 | | - | |
918 | | - | |
919 | | - | |
| 917 | + | |
| 918 | + | |
| 919 | + | |
| 920 | + | |
920 | 921 | | |
921 | 922 | | |
922 | 923 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
33 | 33 | | |
34 | 34 | | |
35 | 35 | | |
36 | | - | |
37 | | - | |
38 | | - | |
39 | | - | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
40 | 41 | | |
41 | 42 | | |
42 | 43 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
56 | 56 | | |
57 | 57 | | |
58 | 58 | | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
59 | 71 | | |
60 | 72 | | |
61 | 73 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
22 | 22 | | |
23 | 23 | | |
24 | 24 | | |
25 | | - | |
26 | | - | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
27 | 29 | | |
28 | 30 | | |
29 | 31 | | |
30 | | - | |
31 | | - | |
32 | | - | |
33 | | - | |
34 | | - | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
35 | 40 | | |
36 | | - | |
37 | | - | |
38 | | - | |
39 | | - | |
40 | | - | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
41 | 46 | | |
42 | 47 | | |
43 | 48 | | |
| |||
103 | 108 | | |
104 | 109 | | |
105 | 110 | | |
106 | | - | |
| 111 | + | |
107 | 112 | | |
108 | 113 | | |
109 | 114 | | |
| |||
233 | 238 | | |
234 | 239 | | |
235 | 240 | | |
236 | | - | |
237 | | - | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
238 | 244 | | |
239 | 245 | | |
240 | 246 | | |
| |||
0 commit comments