Rating System Hardening Audit — full-target red team (KR + international)
Status: Active · Last reviewed: 2026-08-12
Owner-commissioned adversarial audit (2026-08-12) of the rating system's TARGET STATE (current engine + S7 interop + rd-inflation/set-scoring/governance/decay additions), attacked by three independent red teams: algorithm/statistics, manipulation/abuse, sociotechnical/compliance/product. All findings code-verified against live migrations (00458/00461/00463/00432/00431/00130/00085) and components. Companion to rating-interop-model.md.
Fixed immediately (migration 00467, live)
get_home_clubbroken by 00461's PK widening — ranked per (club, discipline) row instead of per club; a 24-match home club split across disciplines lost to a 15-match single-row club →matches.is_cross_poolstamped wrong → wrong confidence increments. Now aggregatesSUM(elo_matches_played)per club.- Confidence bonus bypassed the peak floor — the ±10% bonus applied as a bare
rating + delta, able to push a loser belowround(peak*0.80), breaching the engine's one universal invariant. Now clamps the applied delta per row and records the clamped value inelo_history.
Launch-blocking for KR (owner sign-off then implement)
- Host-only outcome stamping is a permanent loss-suppression channel (abuse C1). A playing host can stamp any losing match
abandoned/walkover/non_competitive→ zero rating impact, real score still displayed, and NO write path can ever restore the outcome (score corrections preserve outcome by design). Fix: non-host co-sign for no-rating outcomes (mirrors the two-sided score pattern), plus a signal on every no-rating outcome for auditability. - Unrated accounts skip every tier gate (abuse C2).
rsvps_enforce_tierskipsunrated/self_assessed— a fresh (or throwaway) account's first RSVP into any tier-restricted session is never checked. Fix: gate unrated accounts on their self-declared seed instead of skipping; phone-verify friction before first tier-gated RSVP until 본인인증 ships. - Loss-recording avoidance + host-as-referee (socio F2/F21). Face-sensitive losses can quietly go unrecorded or be reclassified friendly post-hoc; the score-correction proposer is the host who may be a participant. Fix: friendly is a session-level declaration BEFORE play, not post-hoc; disputed + cross-pool matches need multi-party confirmation; per-member played-vs-scored ratio surfaced to club admin.
- Singles/mixed bands shown without projection labels (socio F3 — live invariant violation).
TierInfoSheetprints singles bands for players with zero singles matches, violating the config's own "projections must be labeled" rule. One-file fix + 참고용 styling. - Demotion shield (socio F13). No grace period: one bad loss after promotion = instant visible demotion — worst-case interaction with 체면 dynamics. Fix: hold
display_tierfor 3–5 matches after promotion (LoL-precedented, display-layer only).
High-value engine corrections (S7 rides)
- Doubles credit assignment (algo C1): both partners get the same delta magnitude (K-scaled only) — the weaker partner of a carry gains MORE per win than the carrier, compounding forever. Fix: weight per-partner delta by contribution to team-average.
- Sandbag window (algo C3 + abuse M4): STRIKE_K only fires matches 5–19 vs the player's OWN seed — a disciplined day-one sandbagger never triggers it. Fix: re-arm strikes past match 20 on large delta streaks; base detection on opponent-pool-implied skill, never self-report.
- Margin gaming is live today (algo M1 + abuse M3):
score_factor(1.5×/1.2×) already imports UTR's run-up-the-score incentive and softens deliberate narrow losses. Fix: apply the multiplier only when team-effective gap is small (blowouts vs much-weaker opponents are expected, uninformative). - Mixed cold-start (algo M4): every first mixed row starts at hard 1000 regardless of doubles/singles skill. Fix: seed new discipline rows from a doubles-weighted cross-discipline prior, labeled as seed.
rdis decorative + name collision (algo M3):rdnever grows, never enters K/expected-score;rating_confidenceis an unrelated trust scalar sharing the "confidence" name. Fix: renamerating_confidence→verification_trustBEFORE any rd-inflation work; wire rd properly only via the held Glicko/OpenSkill port (don't half-wire).- Display fallback lies (algo C2): established singles-only player shows a default-1000 doubles "Silver" badge. Fix: fall back to the singles tier (labeled projection) or a distinct no-doubles-data state.
- Calibration layer (algo M5/M6): blend prior→posterior by
min(n/30,1)instead of a hard cutover; enforce monotonicity on ordinal fits; anchors for fitting must beapi_verifiedonly (abuse M2 — Sybil screenshot rings clear a flat count threshold). - Pump-club detection (abuse C3): global ratings + self-owned clubs + self-service verification = collusion pump with zero cross-club signal. Fix: weekly offline job flagging closed-clique match graphs / outlier velocity → moderation queue (detection, not gates).
- Decay policy recommendation: uncertainty-inflation only (Glicko-style), never LK-style rating decay — KR outdoor-season winter breaks must not cost rating points.
Compliance deltas (pre-expansion, some pre-KR)
- GDPR Art. 22 adjacency (F7): tier gates are fully automated denial of session entry — EU launch needs a visible human override path (host authority extension) + block-reason transparency.
- Age gate is hardcoded 14 (F9): PIPA-correct, GDPR-wrong for DE/FR/NL (16/15) — must become a per-market config value; cheap now (clean-break window), expensive later.
- Erasure vs evidence (F8): S7's
evidence JSONBmust never store raw screenshots (third-party PII) — numeric extraction only; anonymization cron column list must explicitly coverelite_background*. - Consent memo (F10): name the "contract necessity" basis for rating computation explicitly before EU.
- Leaderboard as market feature flag (F18): public skill display is a per-market product decision (JP's soft-label culture), not a formatting swap.
Cheap high-leverage product wins
- Demotion shield (above). 2. Surface
eloToNextTier()— the function exists, unused; turns band-edge anxiety into progress motivation. 3. Leaderboard "you + nearby" default view (small-club bottom-of-list shame). 4.ReliabilityPillon every leaderboard row (women's-pool volatility exposure, cold-start honesty). 5. Instrument demotion→attendance correlation (F17) before further tier polish — session attendance is the metric that matters.
What survived the attack unscathed
Three-track independence (consequences need handling, principle held) · hidden-precise/public-coarse display structure · hub-and-spoke interop with never-fuse · the deletion/anonymization pipeline architecture (soft-delete + scrub preserves pool integrity through erasure) · demotion copy ("조정됐어요") · US onboarding fit of seed self-rate.