Skip to content

fix(lint): filter-token-unknown states the runtime's refusal, FILTER_TOKEN_UNKNOWN / 400 - #24312

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-24293-filter-token-message
Oct 18, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-24293-filter-token-message

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

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:

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):

  // #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):

  // #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

…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>
@github-actions github-actions Bot added size/s documentation Improvements or additions to documentation tests tooling labels Oct 18, 2026
@github-actions

github-actions Bot commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/lint, touching 3 documentable anchor(s). ⚠️ 1 changed file(s) yielded no anchor (packages/lint/src/validate-dataset-references.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/deployment/validating-metadata.mdx (via AUTHORING_RULES (symbol, a top-level const object))
What this run could not see
  • 1 changed file(s) yielded no anchor (packages/lint/src/validate-dataset-references.ts) — pages documenting those are invisible to this run
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 4 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 0b44840dfd59fa03a3d207696384357852b691ff → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 1f99c2e99157be27f41d40c586875001917acd98 — the merge of head f3d1666d95945947e5d3f5fee129fcd6350a3d90 into base 0b44840dfd59fa03a3d207696384357852b691ff, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# 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

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 0b44840dfd59fa03a3d207696384357852b691ff → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对 (seat check) · PR #24312 · domain:spec seat 2 · zhuangjianguo · session session_019oFY6Hi35J7atvgVFnmoaK · 2026-10-18T11:07Z

REWORK (patch round 1, same claim 6248725775, same dev) at head 6be194c5073717794184516ed036d91f783d73b0. One item. It is the dev's own open question, answered A.

  • The item: packages/lint/src/authoring-rules.ts:917–:920, the comment above the validateFilterTokens registration, still says the value "reaches the data engine as a literal and matches nothing. The surface renders empty with no error". It is the card's false sentence, and the dev reports it ships in dist/. The claim's file surface is amended to cover it (6249110232). The comment takes the message's words; comment text only.
  • Held, read by the seat in the diff (not re-asked):
    • The message now reads "The data engine refuses a query that carries it (FILTER_TOKEN_UNKNOWN, 400), so the surface never shows the data the filter meant." That matches UnknownFilterTokenError (core/src/utils/filter-tokens.ts:159–:163, thrown at :547 and :552). The first sentence, rule id, severity, path and hint are unchanged.
    • The module header's "Why this is an error" section now puts the silent-zero history in the past tense.
    • The validate-dataset-references.ts:36–:39 docblock no longer quotes the old sentence (triage 6248475229's added site).
    • The new test asserts the named code FILTER_TOKEN_UNKNOWN on both unknown-token arms, not the prose. The dev's one-off ablation turned it red.
    • The changeset is a patch on @objectstack/lint with Clause-②: no, and its sentences match the diff.
    • Out of scope: objectui's in-memory ValueDataSource is a door the engine never sees. It is read-only inference, and the message claims only a query that reaches the engine, so it does not overclaim: Acceptance notes.
  • After the push: the seat checks the new head. No contract review is owed: the diff is packages/lint only, Clause-②: no, and not governed.

Generated by Claude Code

…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>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对 (seat check) · PR #24312 · domain:spec seat 2 · zhuangjianguo · session session_019oFY6Hi35J7atvgVFnmoaK · 2026-10-18T11:37Z

ACCEPT at head f3d1666d95945947e5d3f5fee129fcd6350a3d90 (claim 6248725775, patch-round report 6249431309 on #24293). It lands once every check on this head is green. No contract review is owed: the diff is packages/lint plus a changeset, Clause-②: no, and it is not governed.

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

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 18, 2026 11:42
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 18, 2026 11:42
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 18, 2026
Merged via the queue into main with commit 85be049 Oct 18, 2026
48 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-24293-filter-token-message branch October 18, 2026 12:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/s tests tooling

Projects

None yet

2 participants