fix(knowledge): send tag filters as a JSON string so the document filter works#5259
fix(knowledge): send tag filters as a JSON string so the document filter works#5259waleedlatif1 wants to merge 6 commits into
Conversation
…ter works The document-list tag filter never reached the database. The `tagFilters` query field was a Zod `.transform()` that decoded the JSON string into an array of objects; the client's `requestJson` parses the query before serializing, so `appendQuery` received the array and emitted `tagFilters=[object Object]` into the URL. The route then failed to `JSON.parse` it and returned 400, so the list came back empty (or stale via keepPreviousData) regardless of operator or value. - Model `tagFilters` as the wire string it actually is; decode it server-side via a new `parseDocumentTagFiltersParam` helper (route maps a bad value to 400). - Harden `appendQuery`: throw on an array-of-objects query param instead of silently serializing `[object Object]`, so this whole class fails loudly. - Default the text tag-filter operator to `contains` so a partial value matches. - Tests: requestJson serializes the JSON param verbatim + the guard throws; the query schema keeps tagFilters a string; the decode helper round-trips. A full sweep of every GET/DELETE contract query field confirmed this was the only field of this class — logs filters and table filter/sort are unaffected.
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
PR SummaryLow Risk Overview Contract & API: Client: UI: Text tag filters default to Reviewed by Cursor Bugbot for commit de73d43. Configure here. |
Greptile SummaryThis PR fixes Knowledge Base document tag filters so they reach the API in the expected wire format. The main changes are:
Confidence Score: 5/5This looks safe to merge.
Important Files Changed
Reviews (6): Last reviewed commit: "fix(knowledge): reject impossible calend..." | Re-trigger Greptile |
Greptile P2: documentTagFilterSchema accepted any non-empty operator string, so an unsupported operator was silently dropped by the query builder instead of returning 400. Validate the operator against the field type's allowed set (single source of truth in filters/types) via superRefine.
|
@greptile |
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 00d4872. Configure here.
…ient claim Greptile P1: operator validation trusted the client-supplied fieldType, so a numeric slot could be sent with fieldType 'text' + 'contains' and slip through to build a text LIKE on a numeric column. Validate against the slot's inherent type via getFieldTypeForSlot (the source of truth): reject unknown slots and fieldType/slot mismatches at the boundary before checking the operator.
|
@greptile |
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 7a68802. Configure here.
Greptile P1: value/valueTo were z.unknown(), so a number filter accepted 'abc', a date filter 'not-a-date', etc. — unusable values the query builder then silently dropped. Add a shared isValidFilterValue (single source of truth in filters/types) and reject unusable value/valueTo at the boundary, including the between upper bound.
|
@greptile |
|
@cursor review |
Cursor Bugbot: the strict valueTo validation made a partially-entered between filter (lower bound only) 400 and break the whole document list mid-entry. activeTagFilters now withholds a between row until both bounds are filled — consistent with already requiring the lower bound before sending any filter — so the list keeps loading while the range is being entered.
|
@greptile |
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 5f4fe13. Configure here.
Greptile P1: the date value check was format-only, so 2026-02-30 / 2026-99-99 passed the boundary and then made the document query's ::date cast throw a 500. Validate real calendar dates by round-tripping the parsed parts.
|
@greptile |
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit de73d43. Configure here.
Summary
The Knowledge Base document-list tag filter never reached the database — it returned empty for every filter, regardless of operator or value. (Earlier #5221 fixed the backend matching, but the request never got there.)
Root cause: the
tagFiltersquery field was a Zod.transform()thatJSON.parsed the string into an array of objects. The client'srequestJsonparses the query schema before serializing, soappendQueryreceived the array and emittedtagFilters=[object Object]into the URL. The route'sJSON.parsethen threw → 400 → the list came back empty (or stale viakeepPreviousData, which is why the document still appeared in the reporter's screenshots even though the filter "did nothing").Changes
tagFiltersis now modeled as the wire string it actually is. Decoding into typed filters happens server-side via a new exportedparseDocumentTagFiltersParamhelper; the route maps a malformed value to a 400.appendQuerynow throws on an array-of-objects query param instead of silently serializing[object Object]— this whole bug class now fails loudly at the boundary.contains(a partial value matches), instead of exactequals.requestJsonserializes a JSON-string query param verbatim and the guard throws on array-of-objects; the query schema keepstagFiltersa string (guards against reintroducing the transform); the decode helper round-trips and rejects malformed input.Scope / "is this anywhere else?"
A full sweep of every GET/DELETE contract query field across
lib/api/contracts/**confirmedtagFilterswas the only field of this class. Explicitly verified safe: the logs filters (level/workflowIds/folderIds/triggersare plain comma-joined strings), tablefilter/sort(non-array objects, round-tripped viaJSON.stringify), and theJSON.parsecases in tables/credentials/mcp (all request bodies, not query).Type of Change
Testing
check:api-validationclean.Checklist