# F-143 — Implementer evidence ## What Establishes the shared reporting filter contract + RBAC foundation (`reporting` module, backend-only, no DB reads, no migration). New routes `GET /reporting/filters/schema` (REPORTING_VIEW) and `GET /reporting/filters/validate` (REPORTING_SALES); zod `reportingFiltersSchema` (`[from,to)` semantics), `comparisonRange` helper, and a role→permission map (`REPORTING_ROLE_PERMISSIONS`) + `requireReportingPermission`. Wired in `build-app.ts` inside `if (deps.pool)` with `combinedAuth`. ## Design recap (architect-approved — see architect.md) - `reporting/` layered api→application→domain (mirrors pricing): pure types `domain/filters.ts` (const arrays `REPORTING_COMPARISON/CHANNELS/GROUP_BY` derive their union types DRY); zod schema + parser + `comparisonRange` + `REPORTING_FILTER_META` in `application/filters.ts`; `registerReportingRoutes` in `api/reporting.routes.ts`; public surface in `index.ts`. - From/to are `z.string().refine(!isNaN(Date.parse))` (zod-v4-safe; `.datetime()` API moved in zod v4). Range semantics `[from,to)` via `refine(from < to)` → 400 on inversion. Repeatable UUID fields use `z.preprocess` (single string ↔ array) because Fastify querystring yields string vs array. - RBAC: no permission table exists → role→permission map + `requireReportingPermission(user, perm)` (future table migration keeps call-site signature). FINANCIAL/EXPORT admin-only; editor/pos_* least-privilege. - `dataAvailability` is metadata only (F-142 §4 baseline); never converts unavailable→0; no report data computed (F-144+). - Routes have NO 200 response schema (broad passthrough, matching security.routes.ts precedent) so the dynamic DTO isn't stripped by fast-json-stringify; error responses use the shared `errorSchema`. ## Files created - `project/src/modules/reporting/domain/filters.ts` — const arrays + pure types (`ReportingFilters`, `ReportingFilterMeta`, `DateRange`, `ComparisonRange`, `DataAvailability`, etc.). - `project/src/modules/reporting/domain/permissions.ts` — `ReportingPermission`, `REPORTING_ROLE_PERMISSIONS: Record`, `requireReportingPermission`, `userReportingPermissions`. - `project/src/modules/reporting/application/filters.ts` — `reportingFiltersSchema`, `parseReportingFilters`, `comparisonRange`, `REPORTING_FILTER_META`, `REPORTING_PAGE_SIZE_MAX`. - `project/src/modules/reporting/api/reporting.routes.ts` — `registerReportingRoutes` + `ReportingRoutesDeps { authenticate }`. - `project/src/modules/reporting/index.ts` — public re-exports. - `project/src/modules/reporting/tests/filters.test.ts`, `tests/permissions.test.ts`, `api/reporting.routes.test.ts`. - EDIT `project/src/app/build-app.ts` — `import { registerReportingRoutes } from '../modules/reporting/index.js'` + register block inside `if (deps.pool)` using `combinedAuth`. ## Tests - NEW `reporting/tests/filters.test.ts` (9): defaults, repeatable uuid (single+array), from>=to→throw, invalid datetime, pageSize ceiling, invalid uuid→throw, comparisonRange (none/previous_equal/previous_calendar). - NEW `reporting/tests/permissions.test.ts` (6): admin all-grants, pos_cashier least-privilege, customer none, requireReportingPermission 403 for denied, FINANCIAL admin-only (AC6), matrix covers every role. - NEW `reporting/api/reporting.routes.test.ts` (7): schema route admin 200 + contract; customer 403 (×2); validate 200 + comparison invariants; defaults; inverted→400; missing→400; customer 403. Mirrors `security.routes.test.ts` (minimal Fastify + mock authenticate, no DB) and installs the build-app errorHandler/serializer so AppError(400/403) surface as real status codes. ## Verification - `npx tsc --noEmit` → **0 errors** (strict, noUncheckedIndexedAccess). - `npx vitest run` (full suite) → **229 passed | 57 skipped** (DB itests skipped w/o `TEST_DATABASE_URL`); reporting contributes +22 (15 unit + 7 route); **0 regressions** vs F-138 baseline (209 passed). - `node project/scripts/check-module-boundaries.mjs project/src` → **0 NEW violations** for `reporting/` (imports only `shared/*` + `zod`). The sole remaining violation (`security/routes.ts → log-broadcaster`, an R1 deep-import) is pre-existing (introduced by F-154), untouched by F-143 — confirmed out of scope. - `./scripts/verify.sh` → green (F-143 `in_progress` is runtime-consistent; no `pending`/`done` mismatch).