Repository navigation
fix(lint): filter-token-unknown states the runtime's refusal, FILTER_TOKEN_UNKNOWN / 400 - #24312
Conversation
…TOKEN_UNKNOWN / 400 The finding's message said an unresolvable placeholder is sent to the data engine as a literal string, matches no record, and the surface renders empty. The data engine refuses such a query instead (UnknownFilterTokenError in @objectstack/core, FILTER_TOKEN_UNKNOWN / 400). The message, the module header, the severity field's doc and the dataset-references docblock that quoted the old sentence now say so. A test holds the message to the code. Claude-Session: https://claude.ai/code/session_019oFY6Hi35J7atvgVFnmoaK Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
What this run could not see
Coarse fallback — 4 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 1f99c2e99157be27f41d40c586875001917acd98 && git checkout 1f99c2e99157be27f41d40c586875001917acd98
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 0b44840dfd59fa03a3d207696384357852b691ff f3d1666d95945947e5d3f5fee129fcd6350a3d90 && git checkout -B drift-repro 0b44840dfd59fa03a3d207696384357852b691ff && git merge --no-ff f3d1666d95945947e5d3f5fee129fcd6350a3d90
node scripts/docs-audit/affected-docs.mjs --json 0b44840dfd59fa03a3d207696384357852b691ff
|
席位核对 (seat check) · PR #24312 ·
|
…untime's refusal The comment above the rule's registration in authoring-rules.ts said an unresolvable filter placeholder reaches the data engine as a literal, matches nothing, and the surface renders empty with no error. The data engine refuses such a query (FILTER_TOKEN_UNKNOWN, 400). The comment now says so and keeps the silent-zero history it was written against, in the past tense. Claude-Session: https://claude.ai/code/session_019oFY6Hi35J7atvgVFnmoaK Co-authored-by: Claude <noreply@anthropic.com>
席位核对 (seat check) · PR #24312 ·
|
| item | reading |
|---|---|
| Shape | Draft, base main. The body's line 1 is Fixes #24293, line 3 is Clause-②: no; no other closing keyword. 5 files: the changeset (@objectstack/lint patch, Clause-②: no) and 4 under packages/lint/src. |
The patch-round item (REWORK 6249122088) |
Fixed by f3d1666d9, comment text only, authoring-rules.ts:917–:921. It now says the data engine refuses the query (FILTER_TOKEN_UNKNOWN, 400) and keeps the #3574 silent-zero history in the past tense, as the module header has it. That matches UnknownFilterTokenError in packages/core/src/utils/filter-tokens.ts. The file surface was amended first (6249110232). |
| Held from round 1 | The message, the module header, the validate-dataset-references.ts docblock, and the new code-pinned test on both unknown-token arms. They are unchanged since the round-1 read. |
| PR body | The seat applied the dev's 7 old_str/new_str pairs. Each matched the live body exactly once. The result is byte-identical to the dev's copy (11097 bytes, read back identical). Fixes #24293 is still the only closing declaration. The authoring-rules.ts item has moved from Acceptance notes into the fixed table. |
Local runs (dev, on f3d1666d9) |
lint build exit 0. vitest 6858 passed (6858): the 5 round-1 skipIf cases ran against the rebuilt dist/. typecheck exit 0. Gates: 64 derived, 62 run with exit 0, 2 NOT-MEASURED (exit 3, a missing dist prerequisite), reconciled with --ran. |
| Out of scope | objectui ValueDataSource (in-memory, sends no query): Acceptance notes, read from code, NOT MEASURED. The message claims only a query that reaches the engine, so it does not overclaim. |
| Landing | After every check on this head is green: pr_ready then automerge_enable. The merge closes #24293. |
Generated by Claude Code
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: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 atvalidate-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
21070a2f0apackages/lint/src/validate-filter-tokens.ts:236–:237, the messagevalidate-filter-tokens.ts:25–:26, the module header ("passed to the data engine verbatim, matches no row")UnknownFilterTokenErrorin@objectstack/corevalidate-filter-tokens.ts:28–:41, the header's "Why this is an error" section ("The runtime failure mode is silent", present tense)validate-filter-tokens.ts:106, theseverityfield doc ("silently matches nothing")packages/lint/src/validate-dataset-references.ts:36–:39, the docblock quoting the old message (triage6248475229)filter-token-unknownreports is refused loudly, while a dangling field path at the same node is the quiet failure, per that file's own severity notepackages/lint/src/authoring-rules.ts:917–:920, the comment abovevalidateFilterTokens' registration ("reaches the data engine as a literal and matches nothing. The surface renders empty with no error"). The comment ships inside@objectstack/lint'sdist/index.jsanddist/runtime.jsf3d1666d9, comment text only. The claim's file surface was amended to add it (6249110232). The#3574provenance and the silent-zero history are kept, in the past tense, as in the module header. Before and after are belowThe
authoring-rules.tscomment before (21070a2f0,:917–:920):and after (
f3d1666d9,:917–:921):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
21070a2f0apackages/core/src/utils/filter-tokens.ts:resolveFilterTokensthrowsUnknownFilterTokenError(status = 400,code = 'FILTER_TOKEN_UNKNOWN') for a whole-string placeholder outside both vocabularies (:547), and for one the resolver drops (:552).find,findOne,count,aggregate, throughresolveWhereTokens) and onupdate/delete(withResolvedWhere); the analytics service onwhere,timeDimensions[].dateRangeand a dataset's own filter and measure filters (analytics-service.ts:1852–:1883,dataset-executor.ts:128).packages/objectql/src/engine-filter-token-today-spelling.test.ts:71–:74(FILTER_TOKEN_UNKNOWN/ 400) andpackages/rest/src/rest-aggregate-positions.test.ts:171, which asserts_status400 andcodeFILTER_TOKEN_UNKNOWNatPOST /api/v1/data/:object/queryfor awherecarrying'{not_a_token}'.packages/core/src/utils/filter-tokens.tsthere,resolveContextTokens) passes an unknown token through untouched, so it reaches the server and is refused there.Tests
On head
f3d1666d9(round 1's6be194c50, plus theauthoring-rules.tscomment 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 inpackages/lint/srcand 0 inpackages/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 that6be194c50's run skipped (6853 passed | 5 skipped) are theskipIfcases inlazy-deps.test.tsandruntime-lazy-deps.test.ts. They need lint's own builtdist/, which that run did not have. This time they ran, against the rebuiltdist/.pnpm --filter @objectstack/lint typecheck:VERDICT command-exit 0.check:test-typecheck: OK.tsc -p tsconfig.test.json --listFileslistsvalidate-filter-tokens.test.ts, so the test layer compiles the new case.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 codeFILTER_TOKEN_UNKNOWN, not to the prose around it.git grepfor 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 at32b492b67finds nothing.packages/cli/test/authoring-rule-command-parity.test.ts:107names the rule id only.scripts/ablation-replace.mjs(anchor hit x1 to x0, blob47d8294046cftoa81f9a88dae0): 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 containthe code. Restored: blob equals HEAD (47d8294046cf),git diff HEADempty,git statusclean. The test imports the source by relative path, so nodist/sits between the mutation and the run.Gates (local subset; the whole farm is CI's)
Derived on head
f3d1666d9withnode scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, no paths given (change set: these 5 files vs merge base21070a2f0). That gives 64 commands, the same set as on6be194c50, 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.pnpm check:docs-transcript-driftamong them, since lint'sdist/was built this time. On6be194c50, 61 exited 0 on the first pass. These includecheck: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/mainandcheck-empty-changeset.mjs --base origin/main.pnpm check:docs-transcript-drift: on6be194c50it exited 3 on the first pass (PREREQUISITE NOT MET: nopackages/lint/dist), then 0 after a lint build. Onf3d1666d9it 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 builtdist/index.jscarries the new message, and the old sentence is gone from it (0 hits).pnpm check:dual-build-cjs-loadsneeds every package'sdist/(a wholepnpm build), andpnpm check:lean-entry-closureneeds@objectstack/objectql'sdist/. Both are left to CI.origin/mainhas moved past the base (2ee5861b0,0c3db0f6a,30da04f8eand later). None of these commits touchespackages/lint, its build closure, orpackages/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 onf3d1666d9flags 4 gate inputs that changed onmainacross that range:scripts/check-single-authz-resolver.mjs,scripts/slot-lookup-baseline.json,scripts/check-route-envelope.mjsand onescripts/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
ValueDataSource(provider: 'value',packages/core/src/adapters/ValueDataSource.tsat objectui32b492b67) 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.rest-aggregate-positions.test.ts:171), not re-run here. CI'sTest Coreruns it.Generated by Claude Code