Estimate: 32m · Depends on: 4.6.3, 6.1.2, 6.2.1
The aggregation reads every widget and report page consumes — extending reportsService (the home 4.6.3 creates). Pure backend.
getCreatedVsResolved(scope, { period, daysBack, cumulative }) — scope = { projectId } | { savedFilterId } (the filter resolves through THE 6.2.1 resolve-by-id contract — decode + registry-validate via 6.1.1, never trust-and-compile — into the WHERE fragment the buckets scope to). Two series: created = createdAt bucketed by day/week/month over the days-back window; resolved = NET transitions into a done-category status (getTerminalStatusKeys — the recorded deviation: our resolution IS the done category) derived from the 1.4.6 revision trail via ONE bounded grouped $queryRaw (the 4.6.3 pattern; a reopen inside the window subtracts). cumulative: true running-sums within the window server-side. Window capped (e.g. ≤366 days, ≤120 buckets) → typed 422 beyond.
getDistribution(scope, statisticType) — a bounded GROUP-BY count over the scoped items, through a TOTAL statistic-type registry: the enum-ish field vocabulary the 6.1 registry already enumerates (kind, status, priority, assignee, reporter, sprint, label, component, select-CF cf:<id>, user-CF) — label/component/CF group-bys ride the SAME 5.3.1/5.4.1 indexed joins 6.1.2 compiles (one item counted once per label ⇒ multi-label items appear in multiple segments, the Jira behaviour — documented); unknown statistic ids → 422. Returns segments (id, label, count, percentage) + the total; a NULL group surfaces as the designed "None" segment.
getFilterResultsPage(scope, page, pageSize ≤ 50) — rides the EXISTING 2.5.8/2.5.12 list read + count with the compiled fragment (no second query path; the verified 50/page gadget cap enforced server-side).
Per-VIEWER gating (the 6.4 seam). Every read resolves access for the REQUESTING user: a project-sourced scope checks 6.4 project access; a filter-sourced scope rides the 6.2.1 resolve (already behind the 6.4 browse gate + filter visibility for the CALLER — pass the viewer, never the owner). Denied → a typed no-access result (the widget state), NEVER partial data or a leaked count. Stale referents (deleted filter/project/statistic referent) → the typed stale result (the 6.1.2 unknown-value precedent).
pnpm test:coverage ≥90%.reportsService + workItemRevisionRepository.aggregate* (the bounded grouped-derivation pattern + the done-category predicate via getTerminalStatusKeys)