Shipped (status: done in frontmatter). Redis-backed caching for SLA master reads (sla_priority, working hours, holidays, custom rules) on the contract portal backend, reusing the same Redis as BullMQ with key prefix cms:sla:v1: — no extra npm dependency (ioredis comes via BullMQ). Includes dashboard batch priority map + calculateWorkingDays calendar short-circuit (SYS-2026, SYS-2024).
backend-contract-portal-dev/src/modules/sla/README.mdcover-letter-dashboard.helper.ts processContractList passes Map<string, slaPriority> into resolveSlaResponseWorkingDayssrc/modules/sla/cache/sla-redis-cache.service.ts (disable with SLA_REDIS_ENABLED=false)sla-fetch.service.spec.ts)Implementation summary from Cursor agent transcript (vault triage 2026-05-13; source inbox capture consumed).
backend-contract-portal-dev). Tracks SYS-2047 (Redis SLA cache), aligned with SYS-1970 perf breakdown (SYS-2026 N+1 priorities, SYS-2024 redundant calendar context).REDIS_HOST / REDIS_PORT / REDIS_PASSWORD); no separate Redis dependency — ioredis not added as a direct package.json dep (still available transitively via BullMQ).SlaRedisCacheService — timeouts, graceful fallback on errors; SLA_REDIS_ENABLED=false disables Redis reads/writes.cms:sla:v1:* (TTLs per README).SlaFetchService — cache hits for priority list, calendar (working hours / holidays), custom rules; invalidateCache / invalidateCustomRulesCache delete matching Redis keys on writes.cover-letter-dashboard.helper.ts processContractList loads sla.getList(true) once → Map<string, slaPriority> passed into resolveSlaResponseWorkingDays (removes per-row findOne).calculateWorkingDays — only getCalendarContext() when holidays or businessHours missing (honours prefetch).src/modules/sla/README.md — keys, TTLs, env flags.sla-fetch.service.spec.ts — mocked Redis client; hit/miss/fallback behaviour.LATERAL join regression (dashboard SQL)invalid reference to FROM-clause entry for table "cvl" on routes using dashboard subqueries (e.g. getCountDashboardLegal), after attempts to satisfy TypeORM’s string-join getTableName() behaviour on cms_schema.table inside LATERAL (...).(SELECT * FROM LATERAL (...) …) — turns lateral into a non-lateral derived table → cvl no longer in scope.runner.helper.ts): Detect raw LATERAL (...) join repos and push a JoinAttribute whose alias uses subQuery (same mechanism TypeORM uses for parenthesized subquery joins). SQL stays LEFT JOIN LATERAL (...) alias ON TRUE. applySubQueryJoin used from selectQueryBuilder and selectQueryBuilderRaw.cover-letter-dashboard.helper.ts getSubQueryDashboard() (reviews, cancel, hardcopy, customer, etc.).