From 744d33e13fbbaa33deda319036b3b4956cefd3b2 Mon Sep 17 00:00:00 2001 From: John O'Keefe Date: Sat, 31 Oct 2026 15:56:23 -0400 Subject: [PATCH] fix(search): keep the empty-qf 400 at the wire MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The empty-tree grammar relaxation (create-modal episodes seed) must not leak into the search deep-link contract: qf={"mode":"all","children":[]} as a QUERY would silently mean "match everything". The seed's meaning — a rules-less collection matching nothing — lives in the stored tree and its composers; the query entry point now rejects a rule-less tree explicitly, preserving the pinned 400 that TestFilterQFValidationAndPrecedence guards (the test caught the leak during this round's full-suite run). --- internal/handlers/media.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/internal/handlers/media.go b/internal/handlers/media.go index bb65040a..bc854e3f 100644 --- a/internal/handlers/media.go +++ b/internal/handlers/media.go @@ -2559,6 +2559,13 @@ func ParseMediaSearchParams(c searchParamSource) services.SearchParams { tree, err := services.ParseFilterTree(qf) if err != nil { params.FilterParseErr = err.Error() + } else if len(tree.Children) == 0 { + // A qf with no rules is not a browse query: the deep-link + // contract answers 400 (pinned). The empty root gained + // meaning only as a STORED collection seed — where the + // composers define it as matching nothing. As a query it + // would silently mean "everything", so reject at the wire. + params.FilterParseErr = "qf tree has no rules" } else { params.FilterTree = tree }