Gamification Consumer
Gamification Consumer
Section titled “Gamification Consumer”Gamification Consumer je jednoúčelový RabbitMQ consumer (F4.GAM.02). Odebírá zprávy z topic exchange gamification.events a je jediným writerem Strapi content-types user-rank/gamification-event (F4.GAM.01). Nikdy nedůvěřuje XP hodnotě z příchozí zprávy — XP se vždy dopočítá server-side z src/xpRules.ts.
Pro pohled na hodnosti, XP a odznaky z pohledu koncového uživatele viz Hodnosti, XP a odměny.
Klíčové údaje
Section titled “Klíčové údaje”- Port: 3800 (jen
/health— tato služba nemá jiný HTTP povrch) - Tech: TypeScript 5 (
strict: true), Node.js ≥20,amqplib^0.10, Axios, logger Pino (kořenovýCLAUDE.mdpinuje Pino pro nové služby) - RabbitMQ: odebírá
gamification.events(topic), routing keysxp.granted/badge.check/streak.tick/easter-egg.roll - Single-writer invariant: žádný jiný kód nesmí zapisovat do
user-rank/gamification-event— obě content-types mají route/policy úroveň zamčenou jen naX-Service-Secret(sencai.space’ssrc/policies/is-self-or-sencai-admin-user-rank.ts)
Architektura
Section titled “Architektura”producenti (cloud-instance lifecycle, runbook-execution, opsloop-consumer, …) → gamification.events (topic exchange, durable) routing keys: xp.granted | badge.check | streak.tick | easter-egg.roll → gamification-consumer-queue (DLQ: gamification-consumer-queue.dlq, bez TTL) → gamification-consumer (src/index.ts → src/messageHandler.ts) → sencai.space (X-Service-Secret): user-ranks, gamification-events, badge-definitions, validate-user → re-publish: rank.up | badge.awarded | easter-egg.triggered (na STEJNÝ exchange — nikdy je sám nekonzumuje, aby nevznikla smyčka) → audit.events (audit.write) pro rank-up/badge-award/easter-egg mutaceuser-rank nemá relaci organisation — hodnost je přenositelná mezi organizacemi (design F4.GAM.01). Tento consumer nikdy nescopuje podle org; organisation_id v příchozí zprávě je jen informativní kontext pro gamification-event.organisation.
Bezpečnostní model — co consumer nesmí předpokládat
Section titled “Bezpečnostní model — co consumer nesmí předpokládat”gamification.events je topic exchange bez kryptografického ověření původce zprávy (stejný charakter jako platform.events fanout u event-store-consumer). Z toho plynou designová rozhodnutí, která je třeba respektovat při jakékoli budoucí úpravě:
- XP se nikdy nečte z payloadu.
src/messageHandler.ts’sstripForgedXp()zaloguje warning a zahodíxp_granted, pokud přijde — skutečná hodnota vždy pochází zXP_MAPvsrc/xpRules.ts(computeXpForAction()). Neodstraňovat, ani “pro pohodlí testování” nezavádět fallback na payload hodnotu. user_idse ověřuje před každým zápisem.strapiClient.userExists()voláGET /api/user-ranks/validate-user/:id(service-only endpoint přidaný spolu s touto službou). Neexistující/podvrženýuser_id→RejectMessageError→ zpráva jde do DLQ, nikdy se nezapíše.manual_admin_grantdává 0 XP automaticky. Tento consumer nemá žádný způsob, jak ověřit, že volající RabbitMQ zprávy byl skutečně sencai-admin — honorovat libovolnou XP hodnotu pro tentoaction_typeby znovu otevřelo přesně tu injection díru, kterou bod 1 uzavírá. Manuální grant se řeší přímo v Strapi admin UI editacíuser-rankzáznamu.- Easter egg — stochastický, s tvrdým per-user ročním capem.
src/easterEgg.ts’srollEasterEgg()— cap (EASTER_EGG_MAX_PER_USER_PER_YEAR) se vynucuje bezpodmínečně, nezávisle na tom, jak je vyladěná pravděpodobnost. - Idempotency = SHA-256(user_id:action_type:source_event_id). Duplicitní doručení stejné zprávy → Strapi vrátí 400 na unique
idempotency_key→ consumer to bere jako no-op (ne chybu, ne druhý XP grant).
action_type whitelist — musí odpovídat Strapi enumu
Section titled “action_type whitelist — musí odpovídat Strapi enumu”src/xpRules.ts’s ACTION_TYPES musí být v lockstepu s sencai.space’s content-type gamification-event enumem action_type. Přidání nové hodnoty vyžaduje změnu na obou místech — jinak zprávy s novým typem tiše skončí zamítnuté (DLQ), i když by validně měly projít.
Automation-score klasifikace (F4.GAM.07)
Section titled “Automation-score klasifikace (F4.GAM.07)”src/automationClassifier.ts klasifikuje akci xp.granted jako automatizovanou vs. manuální a zapisuje verdikt do gamification-event.metadata.is_automated při zápisu (handleXpGranted v messageHandler.ts). sencai.space’s GET /api/organisations/:id/automation-score (F4.GAM.07) toto pole pouze agreguje — klasifikaci samu nikdy neodvozuje.
Jen tři hodnoty action_type jsou pro tuto metriku relevantní (isAutomationScoreEligible()) — přesně ty, které F4.GAM.03 producenti opatřují polem organisation:
provisioning_completed— čtemetadata.source(mirrorcloud-instance.source):'sencai'= automatizováno (přes vlastní cloud-connector automatizaci, ADR-001),'terraform'= importováno z existujícího tfstate (F3.IMPORT.01) — není to platformní automatizace. Chybějící hodnota defaultuje na automatizováno (běžný případ'sencai').runbook_success— čtemetadata.triggered_by(mirrorrunbook-execution.triggered_by):'alert'= automatizováno, cokoliv jiného/chybějící = manuální.incident_resolved— vždy manuální. Ověřeno přímo protiopsloop-consumer/src/services/*.ts— žádný automatizovaný flow nikdy sám nenastaví incident naresolved, jen spustí runbook (zachyceno zvlášť jakorunbook_success) nebo otevře change-request.
Ostatní action_type (bookkeeping — login_streak, badge_awarded, easter_egg_triggered, automation_score_snapshot, manual_admin_grant) isAutomationScoreEligible() vrací false — classifyAutomation() se pro ně vůbec nevolá, jejich metadata zůstává nedotčené.
Audit — proč nevendoruje @sencai/audit
Section titled “Audit — proč nevendoruje @sencai/audit”Všechny tři akce, které tato služba potřebuje (gamification.rank.up, gamification.badge.awarded, gamification.easter_egg.triggered), už jsou v kanonickém AuditAction union (packages/audit/src/types.ts), doplnila je konsolidační vlna Fáze 4. Tato služba přesto používá vlastní malý, volně typovaný (action: string) publisher v src/audit.ts místo vendoringu @sencai/audit — stejný vzor, jaký si pro identickou situaci zvolil forum-connector. Vendoring je teď odemčený (blokující důvod pominul), jen ještě neproveden.
Konfigurace
Section titled “Konfigurace”PORT=3800RABBITMQ_URL=amqp://admin:admin@sencai-mq:5672STRAPI_URL=http://backend:1337SERVICE_SECRET= # sdílený s sencai.space's bare SERVICE_SECRET, ne vlastní GAMIFICATION_SERVICE_SECRETEASTER_EGG_ANNUAL_QUALIFYING_ACTIONS=50000 # odhad kvalifikujících akcí/rok pro tuning pravděpodobnostiEASTER_EGG_TARGET_TRIGGERS_PER_YEAR=12 # cílový počet triggerů/rok, platform-wideEASTER_EGG_MAX_PER_USER_PER_YEAR=3 # tvrdý per-user capČasté problémy
Section titled “Časté problémy”Q: GET /api/badge-definitions vrací 403 pro service-secret volání
A: Opraveno v sencai.space (F4.GAM.02) — global::hybrid-auth kontrolovalo jen ctx.state.user (KC JWT nebo Strapi API token), nikdy X-Service-Secret. find/findOne teď mají vlastní isAuthenticatedOrService() check v controlleru místo policy.
Q: Zprávy končící v DLQ
A: Neznámý action_type, neexistující user_id, malformed JSON, nebo neočekávaná Strapi/network chyba. Zkontrolovat gamification-consumer-queue.dlq (bez TTL, manuální recovery) přes RabbitMQ management UI (port 15672/15673 dle profilu).
Q: easter-egg.roll/streak.tick/badge.check nemají v produkci žádného producenta
A: F4.GAM.03 (hotovo 2026-07-05) navázalo producenty jen na xp.granted (provisioning/runbook/incident) — pro tyto tři routing keys producent nikdy nebyl v scope. Testovat manuálně přes rabbitmqadmin publish nebo RabbitMQ management UI.
Q: Duplicitní XP i přes idempotency guard
A: source_event_id chybí v payloadu nebo se liší mezi logicky stejnými zprávami. Producent musí posílat stabilní source_event_id — consumer to nemůže odhalit/opravit na své straně (stejný trade-off jako event-store-consumer’s timestamp-based fallback).
Související služby
Section titled “Související služby”- sencai.space — cílové úložiště (content-types
user-rank,gamification-event,badge-definition, F4.GAM.01) - F4.GAM.03 producenti — skuteční producenti XP eventů (cloud-instance lifecycle, runbook-execution, opsloop-consumer), navázaní na
xp.granted - F4.GAM.04/.05 — frontend rank widget + notifikace, konzumuje
rank.up/badge.awardedzgamification.events - Architektura — širší service map