fix: enforce no-silent-truncation on every collection read #81

Merged
David merged 2 commits from fix/pagination-invariant-sweep into main 2026-07-02 05:04:07 +02:00
Owner

Problem

YTCLI-26 (PR #80) fixed list_projects after the missing-pagination bug surfaced as a false project 'VAPP' not found in production. That fix addressed one site. An invariant sweep of the whole client found the same defect across many call sites: the reported bug was one instance of a class.

Invariant: a collection read must never silently truncate. When $top is omitted, the YouTrack REST server caps the response at its default page size (42 rows) and drops the tail. Every collection read must page with $top/$skip until a short page, or be explicitly bounded with a stated reason.

Change

Add a shared fetch_all_pages<T> helper (pages $top=100/$skip until a short page, same shape as fetch_all_sprints) so the invariant lives in one place, and route every violating site through it:

Top-level collections: list_boards (/api/agiles), fetch_issues (/api/issues?query), check_work (assigned-issue query and the per-issue work-item probe), list_projects (/api/admin/projects, refactored onto the helper), resolve_vcs_server_id (/api/admin/vcsServers), list_link_types (/api/issueLinkTypes), list_articles (when no explicit --top; an explicit --top stays a deliberate single-page bound), resolve_timeline_ids (/api/issues?query).

Per-issue subcollections that also cap at 42 on active issues: issue-inspect comments and work items, list_work_items_full, list_attachments.

Left unchanged (already compliant): fetch_all_sprints, list_issue_activities, list_users (caller-bounded via required top), the list command auto-paginate and fetch_issues_page. Domain-bounded N/A (capped by the data model well under 42): per-issue sprint membership, per-issue link slots, per-project custom fields.

Tests

Added a wiremock pagination test per converted function (full first page of 100 plus a partial second page returns 101), a direct fetch_all_pages loop test, and a get_project regression proving a project on page two resolves instead of erroring. Client crate: 137 tests (was 126). just pre-commit (fmt, clippy -D warnings, build, test) passes.

Supersedes

This supersedes PR #80: it re-implements the list_projects fix on the shared helper and fixes the rest of the class in one change. Closing #80 in favor of this.

Fixes YTCLI-27. Fixes YTCLI-26.

## Problem YTCLI-26 (PR #80) fixed `list_projects` after the missing-pagination bug surfaced as a false `project 'VAPP' not found` in production. That fix addressed one site. An invariant sweep of the whole client found the same defect across many call sites: the reported bug was one instance of a class. Invariant: a collection read must never silently truncate. When `$top` is omitted, the YouTrack REST server caps the response at its default page size (42 rows) and drops the tail. Every collection read must page with `$top`/`$skip` until a short page, or be explicitly bounded with a stated reason. ## Change Add a shared `fetch_all_pages<T>` helper (pages `$top=100`/`$skip` until a short page, same shape as `fetch_all_sprints`) so the invariant lives in one place, and route every violating site through it: Top-level collections: `list_boards` (`/api/agiles`), `fetch_issues` (`/api/issues?query`), `check_work` (assigned-issue query and the per-issue work-item probe), `list_projects` (`/api/admin/projects`, refactored onto the helper), `resolve_vcs_server_id` (`/api/admin/vcsServers`), `list_link_types` (`/api/issueLinkTypes`), `list_articles` (when no explicit `--top`; an explicit `--top` stays a deliberate single-page bound), `resolve_timeline_ids` (`/api/issues?query`). Per-issue subcollections that also cap at 42 on active issues: issue-inspect comments and work items, `list_work_items_full`, `list_attachments`. Left unchanged (already compliant): `fetch_all_sprints`, `list_issue_activities`, `list_users` (caller-bounded via required `top`), the `list` command auto-paginate and `fetch_issues_page`. Domain-bounded N/A (capped by the data model well under 42): per-issue sprint membership, per-issue link slots, per-project custom fields. ## Tests Added a wiremock pagination test per converted function (full first page of 100 plus a partial second page returns 101), a direct `fetch_all_pages` loop test, and a `get_project` regression proving a project on page two resolves instead of erroring. Client crate: 137 tests (was 126). `just pre-commit` (fmt, clippy -D warnings, build, test) passes. ## Supersedes This supersedes PR #80: it re-implements the `list_projects` fix on the shared helper and fixes the rest of the class in one change. Closing #80 in favor of this. Fixes YTCLI-27. Fixes YTCLI-26.
fix(client): enforce no-silent-truncation on every collection read
All checks were successful
Check / fmt + clippy + build + tests (pull_request) Successful in 15s
caf0e22b79
An invariant sweep after the list_projects fix (YTCLI-26) found the same missing-pagination defect across the client: any collection read issued without $top is capped at YouTrack's 42-row default and silently drops the tail, surfacing as false "not found" errors and truncated lists. The original diagnosis named one endpoint; the sweep found seven top-level collections plus four per-issue subcollections with the identical bug.

Add a shared fetch_all_pages<T> helper (pages $top=100/$skip until a short page) so the invariant lives in one place, and route every violating site through it: list_boards, fetch_issues, check_work (outer query and per-issue work-item probe), list_projects, resolve_vcs_server_id, list_link_types, list_articles (when no explicit --top; an explicit --top stays a deliberate single-page bound), resolve_timeline_ids, list_work_items_full, list_attachments, and issue-inspect comments and work items. Add a wiremock pagination test per converted function (full first page of 100 plus a partial second page returns 101), including a get_project regression proving a project on page two resolves.

#YTCLI-27
#YTCLI-26
Merge origin/main into pagination invariant sweep
All checks were successful
Check / fmt + clippy + build + tests (pull_request) Successful in 15s
Create release / Create release from merged PR (pull_request) Has been skipped
f0e615a0e1
Resolve list_projects: keep the shared fetch_all_pages helper version from this branch over the inline loop merged from #80 (YTCLI-26). Same behavior, invariant in one place.

#YTCLI-27
#YTCLI-26
David merged commit 097bf34208 into main 2026-07-02 05:04:07 +02:00
David deleted branch fix/pagination-invariant-sweep 2026-07-02 05:04:07 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
pandoras-box/youtrack-cli!81
No description provided.