Skip to content

D324 — lint filters by check and paginates

Decision. lint takes check (a name or a list), limit and offset, and answers with findings_in_page, findings_after_filter, offset and, when more follow, next_offset and truncated. A page holds at most lintMaxPage = 200 findings. lint.Filter sorts what it keeps by severity descending, then check, path and message ascending, so an offset names the same finding on every call.

Why. On a KB of ~760 concepts lint returned 2361 findings in one payload; an agent working one check at a time had to download all of it to filter locally. kb_review and work_list already page; lint was the one governance tool with no facet. Findings came out in map-iteration order, which makes an offset meaningless. The cap is a judgment about a caller's context (~150 bytes a finding, ~30 KiB a page), not a protocol limit.

Alternatives rejected. - Filter and page inside lint.Run: it feeds the cache (D294), gate_check and the Atlas endpoint, which must all see the whole set. - Sort in lint.Run: the cache would store a sorted slice for every caller; sorting in Filter costs one sort per call and gives the Atlas endpoint the order for free. - Reject an unknown check: the filter is a view, an empty page already says "none". - Page gate_check too: it is a verdict, not a browser.

Consequences. check is a view filter exactly like severity_min: count, counts_by_check and counts_by_severity are computed before it, and findings_omitted keeps meaning "hidden by severity_min". Without limit a call returns the first 200 findings and next_offset, not the whole list: a caller that needs all pages by following next_offset. Callers that relied on the unsorted order break (none known).

The D285 budget counts the whole tools/list payload, input schemas included, and the agent profile sits within a few bytes of it: the three new parameters were paid for by shortening the lint description, whose detail lives in docs/control-plane.md.