- Shipped
- September 9, 2026 at 5:23 AM UTC
- Author
- Kamo
- Commit
- 8c3b971
The award gate asked for VIEW_ACHIEVEMENTS and stopped there. Rights are not entitlement-filtered, so that right stays granted — and keeps answering true — in an organization whose plan never included Gamification. Five orgs with no org_features row for the app had members accumulating badges behind a nav that correctly hid every way of seeing them: navRegistry asks `organization.isGamification && checkRight(...)`, and the awarding code asked neither half of that. Both halves now, in the two places that can answer them: * AppAvailabilityInterceptor gains /api/security/achievements -> GAMIFICATION, which covers every request a browser makes. Deliberately not **************** that seam carries the cluster secret and no session, so there is no org for the gate to resolve. * GamificationEntitlement covers what a path prefix cannot — four of the five award seams are not member HTTP requests at all. Modelled on MediaService's GamesEntitlement, routed through isAppEffectivelyEnabled, which is the one right answer: applied model, org_features row and subscription matrix together. Any one read alone gives a different and wrong answer. The right is checked before the app: one indexed lookup ahead of a model and subscription resolution, on a path whose caller is sometimes the endpoint that keeps members signed in. Registration is now derived from PREFIX_MAP rather than typed out a second time in WebMvcConfig. Two lists were required and neither could fail loudly — a prefix in the map alone is never routed through the interceptor, and a path registered alone resolves no ServiceType. Both read as a working gate in source. Registering each prefix bare as well as with /** closes a related hole: /** does not match the collection endpoint, so GET /api/security/leads had never once run the gate's matcher. AppAvailabilityGateWiringTest reads the patterns back off a real InterceptorRegistry, so it fails on drift rather than comparing the map to something derived from it.