Skip to main content
← Back to list
01Issue
BugShippedSwamp CLIPublic
Assigneeskeeb

Relationships

#3931 Award grants (showcase approval, admin award points) skip campaign/streak multipliers

Opened by keeb · 10/25/2026· Shipped 10/25/2026

Summary

Point awards written directly to the score ledger don't get the score multipliers that every other grant gets. That includes active double-XP campaigns (swamp.double_xp_window) and the streak/engagement multiplier. Approving a profile showcase pays a flat 5,000,000 points even during a 2x/3x campaign, and so does an admin manual award. Multipliers should apply to every grant source, awards included.

Where

Both award writers hard-code the multiplier and then write through ScoreAwardLedger.writeAward, which bypasses the ClickHouse grant-projection MVs:

  • lib/app/showcase/showcase-reward.ts: createScoreGrant({ ..., source: "award", multiplier: 1, baseAmount: SHOWCASE_REWARD_POINTS })
  • lib/app/admin/award-points.ts: createScoreGrant({ ..., source: "award", multiplier: 1 })

The event-driven projections in infrastructure/clickhouse/init/03-grant-projections.sql resolve _dx_mult per row from swamp.double_xp_window and write multiplier and amount from it. The streak/rolling-active-days multiplier is applied on the event-grant path too (02-score-grants.sql). The award path does neither.

Steps to reproduce

  1. Open a double-XP campaign in /admin/campaigns covering now.
  2. Approve a pending profile showcase (or grant points from the admin award panel).
  3. Read the resulting grant in swamp.score_grants: multiplier = 1, amount = baseAmount.

Expected

An award granted inside a campaign window gets the same stacked campaign multiplier as event grants, and the operative's streak/engagement multiplier, with baseAmount holding the unmultiplied value. Fix it for every award source at once (one shared resolution in the award path or the ledger writer), not per caller, so any future award source picks it up automatically.

Notes

  • The multiplier has to be resolved at grant time and frozen on the grant, just like the event path (monotonic ledger: grant once, never recompute).
  • Idempotency stays keyed on grantId (award:showcase:<id>), so retrying an award must not re-price it.
02Bog Flow
✓OPEN✓TRIAGED✓IN PROGRESS✓SHIPPED+ 1 MOREASSIGNED+ 5 MOREREVIEW+ 10 MOREPR_MERGED+ 2 MORESESSION_SUMMARIZED

Shipped

10/25/2026, 7:56:05 PM

Click a lifecycle step above to view its details.

03Sludge Pulse
keeb assigned keeb10/25/2026, 7:09:46 PM

Sign in to post a ripple.