Estimate: 32m
The interchange shape and the compile path, over the BUILT-IN fields (everything shipped today: kind, status, priority, assignee, reporter, sprint, title/description text, created/updated/due dates, story points, estimate). Pure backend + lib — no UI.
lib/filters/ast.ts — the typed FilterAST (combinator: 'and'|'or', conditions: [{ field, operator, value }], row cap 20) + the versioned URL codec (?filter=v1:<compact-json-base64url>): encode/decode with typed, RECOVERABLE failures (a malformed/foreign param yields an "invalid filter" state object, never a throw into the page), composing with the shipped ?view/?sort/?page params; a lossless upgrade map from the 2.5.4 facet params into AST rows (the basic→advanced conversion).
lib/filters/registry.ts — the per-field-type operator registry: every built-in field → its operator set + value-arity/validation + compile function + value-editor kind (the UI contract for 6.1.3/6.1.4). Operator semantics per the story description (enum is-any/none/empty; text contains/not; number comparisons; date absolute + between + relative-window + empty). TOTAL (mistake #29): unknown field/operator → typed 422; the registry test enumerates every entry's compile×validate×editor triple.
The compiler (repository layer per the 4-layer rule): AST → parameterized WHERE fragment — Prisma where-args where the shape allows, bound $queryRaw params where it does not (relative date windows, the trgm text match). NO string interpolation of user values or field/operator ids anywhere (ids resolve through the registry to fixed column references). The fragment slots into BOTH existing reads — findProjectIssuesFlat/countProjectIssues (the List + count) and the 2.5.1 ancestor-retaining tree read — replacing/superseding the fixed RepoIssueFilter shape (which remains as the degenerate all-AND case so 2.5.4 facets keep working unchanged). Migration: a pg_trgm GIN index on work_item(title, description) for the contains-match (the finding-#57 no-table-scan guard).
and/or combinators, negation, empty, comparisons, and relative date windows all compile correctly (matrix-tested against seeded data); both reads (flat + tree) accept the fragment with identical match sets.pnpm test:coverage ≥90%.lib/issues/issueListFilter.ts + buildRepoFilter + RepoIssueFilter (the shipped substrate this grows; the URL-param conventions)workItemRepository.findProjectIssuesFlat / the 2.5.1 tree read — the two consumers of the fragmentnotes.html mistake #29 (total registries)motir-core/CLAUDE.md (repo layer owns $queryRaw; FK/migration rules); finding #57 (the trgm index)