fix(spec,drivers): the view filter vocabulary and the AST vocabulary now agree (#3948) - #4039
Merged
Merged
Conversation
…now agree (#3948) `VIEW_FILTER_OPERATORS` (`ui/view.zod.ts`) is what an author may declare on a `ViewFilterRule`; `VALID_AST_OPERATORS` (`data/filter.zod.ts`) gates `isFilterAST()`, which decides whether a filter is parsed into a query at all. They disagreed on **8 of 19** members — `equals`, `not_equals`, `greater_than`, `less_than`, `greater_than_or_equal`, `less_than_or_equal`, `before`, `after`. An author could declare any of them, `ViewFilterRuleSchema` validated them and `defineStack` accepted them; then `isFilterAST()` refused the filter, the protocol passed the array through unconverted, and the driver could not apply it. Six of the eight were reachable only in theory because ObjectUI's adapter alias table happened to translate them — so the query path's correctness was resting on a hand-written table in another repository being complete, and for `before`/`after` it wasn't. `AST_OPERATOR_MAP` becomes the single source of truth: `VALID_AST_OPERATORS` is derived from its keys instead of restated, so an operator can no longer pass the gate without having a lowering. The two were independent hand-written lists that happened to agree, with nothing enforcing it. The map gained the eight canonical view spellings plus the squashed/short forms stored metadata carries. New export `canonicalAstOperator(op)` folds every accepted spelling of one comparison onto a single infix form; both drivers call it rather than growing private alias lists, which is what let them accept different vocabularies. `like`/`ilike` are deliberately NOT folded onto `contains` — driver-sql passes them to SQL verbatim, so folding would silently wrap the value in `%…%`. Widening only; no spelling was removed, so nothing stops validating. Regenerated api-surface.json (0 breaking, 1 added — the ratchet caught it). Tests: spec 6922, objectql 1171, driver-sql 487, driver-memory 178. The new parity test was confirmed to fail without the fix (5 failures naming `before`/`after`). Refs #3948 item 3 Co-Authored-By: Claude <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 3 package(s): 109 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
This was referenced Jul 30, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
#3948 item 3 — the root cause. Item 1 shipped in #4029 and turned this class of failure from silent into loud; this removes the cause.
The disagreement
VIEW_FILTER_OPERATORS(ui/view.zod.ts:74) is what an author may declare on aViewFilterRule, and whatViewFilterRuleSchemavalidates against.VALID_AST_OPERATORS(data/filter.zod.ts) gatesisFilterAST(), which decides whether a filter is parsed into a query at all.They disagreed on 8 of 19 members:
equalsis on that list. An author could declare any of them, the schema validated them,defineStackaccepted them — and thenisFilterAST()refused the filter, the protocol passed the array through unconverted, and the driver could not apply it.Six of the eight were reachable only in theory, because ObjectUI's adapter alias table happened to translate them. The correctness of the query path was resting on a hand-written table in a different repository being complete, and for
before/afterit wasn't — which is how this surfaced (objectstack-ai/objectui#2974).What changed
AST_OPERATOR_MAPis now the single source of truth.VALID_AST_OPERATORSis derived from its keys rather than restated:They were two independent hand-written lists that happened to agree, with nothing enforcing it — and the two failure directions are both invisible: a name in the Set with no lowering hits
convertComparison's$${op}fallback and reaches the driver as an unknown$-operator; a name in the Map but not the Set makesisFilterAST()refuse the filter entirely. (Verified they were byte-identical before this change, so the derivation is behaviour-preserving.)The map gained the eight canonical view spellings plus the squashed/short forms stored metadata carries (
notequals,greaterthanorequal,eq,gt, …) —saveMetapersists the authored body verbatim, so those are live in rows, not historical.New export
canonicalAstOperator(op)folds every accepted spelling of one comparison onto a single infix form. Both drivers now call it instead of growing private alias lists — which is precisely what let them accept different vocabularies before #4029.like/ilikeare deliberately not folded ontocontains: driver-sql passes them to SQL verbatim, so folding would silently wrap the value in%…%and change what the query means.Widening only
No spelling was removed, so nothing that validated before stops validating, and there is no stored-data migration. A filter that previously threw (after #4029) or was silently dropped (before it) now compiles.
Verification
@objectstack/specobjectqldriver-sqldriver-memorycheck:api-surface/check:authorable-surface/check:spec-changesThe api-surface ratchet caught the new export exactly as designed (0 breaking, 1 added); regenerated and committed.
The new parity test was confirmed to fail without the fix — removing
before/afterfrom the map produces 5 failures naming them specifically.A note on how this was found
driver-memory's parity test from #4029 iterates
VALID_AST_OPERATORSdirectly, so widening the gate immediately failed it — the drivers' array paths lagged the gate by 20 spellings. That is the test doing its job, and it is whycanonicalAstOperatorexists rather than two more hand-written alias lists. driver-sql had the identical gap and no test to catch it.