fix(list,data): bridge every spec view operator onto the filter AST (#2901) - #2974
Merged
Conversation
…2901) A stored view filter using `before` or `after` came back **unfiltered**. Not an error — every row, silently. `before`/`after` are canonical members of the spec's `VIEW_FILTER_OPERATORS` (`ui/view.zod.ts`), so a view legitimately carries them. They are absent from `VALID_AST_OPERATORS` (`data/filter.zod.ts`), which gates `isFilterAST()`. Neither translation table had an entry, so they reached the wire verbatim, the server's gate rejected the shape, the protocol passed the array through unconverted, and driver-sql then skipped it entirely — no WHERE clause emitted. See objectstack#3948 for the server-side hardening; this is the client half. The trigger is the single-condition case: `toSpecFilter` emits a bare triple for one AND condition, which is the shape that vanishes. Two or more conditions produce a nested array, which reaches the driver's wider switch and throws. Also fixes `mapOperator` emitting `'not in'` with a space — in no spec vocabulary. Arrays never reached the wire (`normalizeFilterCondition` expands them), but a non-array value escaped as an unfiltered query. The new parity guard caught eight more on its first run — `notequals`, `greaterthan`, `lessthan`, `greaterorequal`, `greaterThanOrEqual`, `lessorequal`, `lessThanOrEqual`, `notin`. All are spellings the spec's `VIEW_FILTER_OPERATOR_ALIASES` still folds, and all are live in stored metadata because `saveMeta` persists the authored body verbatim, so the spec's own `z.preprocess` normalization never reaches the row. Rather than enumerate them, `mapOperator` now matches case- and underscore-insensitively, which collapses the class instead of the instances. Guards assert both tables land inside `VALID_AST_OPERATORS` and that every canonical `VIEW_FILTER_OPERATORS` member survives `isFilterAST()` — so the next operator the spec adds fails a test instead of returning unfiltered rows. Requires `@objectstack/spec` as a devDependency in both packages; safe now that all importers resolve to one spec version. Refs #2901, objectstack#3948 Co-Authored-By: Claude <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
This was referenced Jul 30, 2026
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
os-zhuang
added a commit
that referenced
this pull request
Jul 30, 2026
The spec-17 addendum said "5, not 6" and then listed six package names. Six is right: `plugin-list` and `data-objectstack` gained the devDependency in #2974, but the body's original count of 6 came from a 13-package list that omitted `fields`, which also lacks it. Verified against main. The number is used to scope the parity-guard work, so a wrong one misplans it. Refs #2901 Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com>
This was referenced Jul 30, 2026
os-zhuang
added a commit
that referenced
this pull request
Jul 30, 2026
…e whole view vocabulary (#2945) (#2989) `SPEC_TO_BUILDER_OP` resolved 10 of the spec's 19 canonical `VIEW_FILTER_OPERATORS`. The nine it missed — not_equals, starts_with, ends_with, greater_than, less_than, greater_than_or_equal, less_than_or_equal, is_null, is_not_null — are all canonical members a stored view legitimately carries, and each reached the FilterBuilder as a raw spelling its dropdown cannot select. Same defect and cause as #2974, one table over: spellings enumerated by hand. Now derived from the spec's own canonical list and VIEW_FILTER_OPERATOR_ALIASES, matched case- and separator-insensitively. Four canonical operators have no FilterBuilder equivalent and are recorded as asserted `null`s rather than folded onto a near-equivalent, which would rewrite the author's operator on the next save. Also retires BUILDER_TO_SPEC_OP + toSpecFilter — the write direction, dead since the studio's spec-driven inspector replaced buildViewConfigSchema, and objectui's last emitter of `'not in'`, `before` and `after` as filter-AST operators (objectstack-ai/objectstack#3948). @object-ui/components now exports FILTER_BUILDER_OPERATORS so tables that map onto that vocabulary assert against it instead of restating it. Refs #2945, #2901 Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
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.
A stored view filter using
beforeoraftercame back unfiltered. Not an error — every row, silently.Client half of the defect; server hardening is objectstack-ai/objectstack#3948.
The defect
before/afterare canonical members of the spec'sVIEW_FILTER_OPERATORS(ui/view.zod.ts:90), so a stored view legitimately carries them. They are absent fromVALID_AST_OPERATORS(data/filter.zod.ts:352), which gatesisFilterAST().Neither objectui translation table had an entry, so:
objectFilterEntryToAST→normalizeFilterOperatorends?? op→beforereaches the wire verbatimisFilterAST(['close_date','before','2024-01-01'])→ falseprotocol.ts:2757assigns the array tooptions.whereunconverted (it is a conversion gate, not a validation gate)sql-driver applyFilterswalks it — the three elements are all strings, so each hits the string branch and iscontinuedVerified by executing the real spec:
The trigger is the single-condition case.
toSpecFilter(view-config-utils.ts:158) returns a bare triple for one AND condition — the shape that vanishes. Two or more conditions produce a nested array, which reaches the driver's wider switch and throws instead. The failure mode flips on how many conditions the author happened to add.Not a permission bypass, to be precise: row-scoping
$and-composes as a separate arm and survives. The impact is an unfiltered result set on unscoped objects, and a confusing SQL error on scoped ones.Also fixed
mapOperatoremitted'not in'with a space — in no spec vocabulary. Arrays never reached the wire (normalizeFilterConditionexpands them into an AND of inequalities), but a non-array value escaped as an unfiltered query.The guard found eight more on its first run
notequals,greaterthan,lessthan,greaterorequal,greaterThanOrEqual,lessorequal,lessThanOrEqual,notin.All are spellings the spec's
VIEW_FILTER_OPERATOR_ALIASESstill folds — and all are live in stored metadata, becausesaveMetapersists the authored body verbatim (protocol.ts:4481: "The originalitemis kept verbatim"), so the spec's ownz.preprocess(normalizeFilterOperator, …)normalizes for the validity check and then throws the result away.The switch had been enumerating spellings by hand and had already missed eight of them, so
mapOperatornow matches case- and underscore-insensitively — collapsing the class rather than the instances.Guards
Two parity tests, one per package, asserting in both directions:
VALID_AST_OPERATORS;VIEW_FILTER_OPERATORSmember survivesisFilterAST()in the reachable bare-triple shape;So the next operator the spec adds to the view vocabulary fails a test instead of quietly returning unfiltered rows.
Needs
@objectstack/specas a devDependency in both packages — safe now that every importer resolves to a single spec version (#2950).Verification
plugin-list+data-objectstacksuites: 439 pass across 29 files.eslint: 0 errors.🤖 Generated with Claude Code