51 lines
3.2 KiB
JSON
51 lines
3.2 KiB
JSON
{
|
|
"feature_id": "F-143",
|
|
"agent": "reviewer",
|
|
"stage": "review_gate",
|
|
"verdict": "APPROVED",
|
|
"summary": "F-143 reporting module (contracts + RBAC, no DB reads, no migration) approved. Architecture matches the pricing module layering (api>application>domain>shared) and R1/R2 boundaries are respected: reporting imports only shared/* + zod (no other module), and build-app wires it via the public index. The zod schema enforces [from,to) inclusive-start/exclusive-end semantics (from<to refine -> 400 on inversion), repeatable UUID fields accept single-or-array via z.preprocess (Fastify querystring parity), and the two-route/two-permission split (REPORTING_VIEW on /schema, REPORTING_SALES on /validate) exercises RBAC end-to-end. reportingFiltersSchema.parse runs inside parseJson (shared/http-input) so refinement+transform execute on parse and zod issues map to AppError(400,VALIDATION_ERROR). dataAvailability is metadata-only baseline (never converts unavailable->0); no report data is queried (F-144 owns that). build-app.ts registers the module inside if(deps.pool) with combinedAuth (F-154 combined authenticator), matching the thin backoffice-module pattern (no pool needed for F-143 itself, authenticate is the only dep).",
|
|
"checks": [
|
|
{
|
|
"item": "R1: reporting imports only shared/* + zod",
|
|
"ok": true,
|
|
"evidence": "check-module-boundaries.mjs reports 0 reporting violations; sources import '../../../shared/{auth,errors,http-input,swagger}.js' + 'zod' only"
|
|
},
|
|
{
|
|
"item": "R2: build-app imports reporting via public index only",
|
|
"ok": true,
|
|
"evidence": "build-app.ts: import { registerReportingRoutes } from '../modules/reporting/index.js'"
|
|
},
|
|
{
|
|
"item": "single ReportingRoutesDeps (authenticate only — no pool)",
|
|
"ok": true,
|
|
"evidence": "ReportingRoutesDeps { authenticate: Authenticate }; registered inside if(deps.pool) block with combinedAuth"
|
|
},
|
|
{
|
|
"item": "[from,to) range semantics + from<to -> 400 on inversion",
|
|
"ok": true,
|
|
"evidence": "application/filters.ts .refine(from<to); routes.test 'returns 400 on inverted range'"
|
|
},
|
|
{
|
|
"item": "repeatable UUID accepts single OR array",
|
|
"ok": true,
|
|
"evidence": "z.preprocess(string|array->array); filters.test 'accepts repeated and single storeId as a UUID array'"
|
|
},
|
|
{
|
|
"item": "two routes, two distinct permissions (RBAC end-to-end)",
|
|
"ok": true,
|
|
"evidence": "/reporting/filters/schema -> REPORTING_VIEW; /reporting/filters/validate -> REPORTING_SALES; customer->403 on both"
|
|
},
|
|
{
|
|
"item": "dataAvailability is metadata-only baseline (no unavailable->0)",
|
|
"ok": true,
|
|
"evidence": "REPORTING_FILTER_META.dataAvailability hardcoded baseline; grossSales/discounts/tax/unitsSold/orders/customers=available, netSales/margin/paymentMethod/refunds/shipping=unavailable; no metric computation in routes"
|
|
},
|
|
{
|
|
"item": "schema driven by zod (DRY const arrays derive union types)",
|
|
"ok": true,
|
|
"evidence": "domain/filters.ts exports REPORTING_COMPARISON/CHANNELS/GROUP_BY as const; types derived via typeof[x][number]; z.enum uses the const tuples"
|
|
}
|
|
],
|
|
"issues": []
|
|
}
|