# Specialist Report — agent-feature (`typeName` issue list filter) Spec: `docs/specs/20260623-165915-agent-feature.md` (PAPI-3535) ## Summary Adds an optional comma-separated `typeName` query parameter to `GET /api/v2/projects/{projectId}/issues`. When supplied, the issue list is filtered to issues whose `IssueType.TypeName` matches one of the provided values (OR semantics, case-insensitive). Omitting the parameter returns the full, unchanged list. Read-only, additive, backward-compatible. ## Revision addressed (human reviewer feedback) > "Did you do client side filtering even though we're doing a postgres change?" Yes — the original implementation filtered the returned rows in JavaScript via a `filterRowsByTypeName(rows, typeNames)` helper *after* the query ran. That was wrong on two counts: 1. It is client-side filtering, not the intended Postgres-side change. 2. Because `fn_GetIssueList`/`fn_GetIssueListDetailed` apply the page-size `LIMIT` internally, filtering the already-paginated rows in JS could return fewer rows than the page size and silently drop matches that sit on later DB pages. **Fix:** the filter is now pushed down to Postgres. The service appends a `WHERE LOWER("TypeName") = ANY($n::text[])` clause to the function-call query and binds the trimmed, lowercased type names as a `text[]` parameter — the database performs the filtering. This mirrors the existing `= ANY($n::text[])` pattern in `src/services/userfiles.service.ts`. The JS `filterRowsByTypeName` helper was removed and replaced by `applyTypeNameFilter(baseQuery, params, typeNames)`, which leaves the query untouched when no type names are supplied (preserving existing behaviour). ## Files changed in this revision - `src/services/issues.service.ts` - Removed `filterRowsByTypeName` (client-side row filter). - Added `applyTypeNameFilter`, which appends the SQL `WHERE` clause and binds the lowercased `text[]` param. - `getIssueList` / `getDetailedIssueList` now build a params array and call `applyTypeNameFilter` before `query(...)`. - `test/unit/services/issues.service.spec.ts` - Rewrote the `typeName filter` unit suites to assert the SQL text and bound params handed to `db.query` (clause present/absent, `$6::text[]`, trimmed + lowercased array, OR semantics) rather than client-side filtering of rows. ## Files from the original change (unchanged this revision) - `src/api/v2/projects/issues/issues.controller.ts` — parses the comma-separated `typeName` param into a trimmed, non-empty `string[]` (or `undefined`) and passes it to the service. - `src/api/v2/projects/issues/issues.validator.ts` — 400s when `typeName` is present but empty/non-string. - `src/api/v2/projects/issues/issues.routes.ts` — wiring. - `src/models/ingress.ts` — `typeName?: string` on `IssuesQueryParam`. - `test/e2e/api/issues.e2e.spec.ts` — end-to-end coverage (match, case-insensitive, OR semantics, empty result, omitted param, 400 on empty). These are behaviour-level and remain valid since the observable endpoint behaviour is unchanged by moving the filter into SQL. ## Verification - `npx tsc --noEmit` — passes. - `npx mocha --require ts-node/register test/unit/services/issues.service.spec.ts` — 13 passing (including the 7 rewritten typeName tests). - E2E suite not run here (requires the seeded DB/integration environment); the pipeline runs it afterward. ## Deviations from spec None. The filter is implemented server-side as the spec intends. The DB stored functions (`fn_GetIssueList`, `fn_GetIssueListDetailed`) are not defined in this repo, so the Postgres-side filter is applied by wrapping the function-call query with a `WHERE` clause rather than altering the function signature — no schema or function migration is required, consistent with the "no schema/column changes" constraint in Out of scope. ## Open questions for the reviewer - Page/filter interaction: the `WHERE` clause filters the rows returned by the paginated function, so a page can contain fewer than `size` matches even when more matching issues exist on later DB pages. This matches the existing pagination semantics and the spec lists pagination changes as out of scope, but if the dashboard needs "N matches per page" we'd need the filter pushed *inside* the stored function (a DB migration) — out of scope here. Confirm the current behaviour is acceptable.