Skip to main content
← Back to list
01Issue
BugOpenSwamp Club
AssigneesNone

Relationships

#501 username_metrics projection backfill does not trigger re-scoring (stale UserScore for dormant users)

Opened by keeb · 5/31/2026

Summary

The one-time username_metrics projection backfill introduced in #491 corrects the telemetry projection but never drives a score refresh. As a result, users whose pre-association history was credited by the backfill but who have had no authenticated activity since the deploy keep a stale UserScore (canonical/activity score), even though the corrected data is already in username_metrics.

Background

#491 made username_metrics a materialized projection of identity_map -> user_metrics, folding a device's pre-association history into its owning account. A one-time backfill recomputes every existing mapping at startup.

There are two distinct artifacts:

  1. The telemetry projection (username_metrics), read live by GET /v1/stats?username=. The profile's live stats block reads this directly, so it reflects the correction immediately.
  2. The stored UserScore in the main app (canonicalScore, activityScore, scoreSources, scoreHistory). This is written only when a score refresh runs — it is not recomputed at read time.

Root cause

The only trigger for a score refresh is the telemetry watcher's onBatchDelivered -> writeToSwampClubQueue (services/telemetry/lib/queue-producer.ts), which builds its username set from events present in the ingested batch (if (event.username) usernames.add(...)) and publishes a score-refresh message the main app consumes (refreshUserScoreFromApi).

The backfill (backfillUsernameMetricsProjection in services/telemetry/lib/consumers/stats.ts) calls recomputeUsernameMetricsProjection only — it writes username_metrics and publishes nothing to the score queue. The main app's own one-time runUserScoreBackfill is migration-gated and does not re-run on restart. So nothing re-scores the backfilled population.

Impact by cohort

  • Active users: score self-heals — each ongoing authenticated event enqueues a refresh that reads the corrected projection.
  • On-bind (a dormant user runs one authenticated command): correct — the binding event carries a username, so it is in the batch and triggers a refresh.
  • Backfilled + now dormant: projection is correct, but UserScore is stale until the user's next authenticated event (a CLI command, or even a web sign-in). The live stats block masks this because it reads the projection directly, not the stored score.

Sibling gap

A steady-state anonymous event on an already-bound device updates the projection (via the ingest recompute) but does not enqueue a refresh either, because the batch carries no username. The score lags until the next authenticated event for that user.

Proposed fix

After the backfill recomputes a username's projection, publish a score-refresh message for that username — reuse the same scoreRefreshQueue the producer uses, batched over the recomputed usernames. The main app then re-scores from the corrected projection with no new event required. This is the missing second half of the one-time backfill (data half landed in #491, score-propagation half did not). Optionally also enqueue a refresh from the ingest recompute path when an anonymous event updates an already-bound username, to close the sibling gap.

Affected

  • services/telemetry/lib/consumers/stats.tsbackfillUsernameMetricsProjection, the ingest recompute path
  • services/telemetry/lib/queue-producer.ts — score-refresh publish (reuse)

Follow-up to #491.

02Bog Flow
OPENTRIAGEDIN PROGRESSSHIPPED

Open

5/31/2026, 4:05:48 AM

No activity in this phase yet.

03Sludge Pulse

Sign in to post a ripple.