Relationships
#507 Feed-post scoring is a direct domain write, not a consumer of feed_post_approved telemetry
Opened by keeb · 6/1/2026· Shipped 6/9/2026
Summary
Points for an approved feed post are awarded by a direct domain write (contributeFeedPostScore) inside judgePost, not by a consumer of the feed_post_approved telemetry event. The only consumer of that event is the telemetry service's DiscordConsumer (which posts the embed). There is no scoring consumer. This split is what made the #502 orphan unrecoverable by replay and forced a bespoke one-off backfill.
Current behavior
On approval, lib/app/feed/judge-post.ts does two unrelated things down two different roads:
- Points (direct-write road):
contributeFeedPostScore(...)writes acontent:feed-post:<id>contribution straight intouser_scores, plus anevent_queuerefresh nudge soprocessScoreBatchrecomputes totals. - Discord (telemetry road):
announceFeedPost(...)emitsfeed_post_approved→ telemetry serviceDiscordConsumer→discord_event_queue→ discord-bot.
Nothing on the telemetry/consumer side awards score. I traced every consumer of feed_post_approved: only services/telemetry/lib/consumers/discord.ts handles it.
Why this is bad
- Not replayable. Re-ingesting / re-firing
feed_post_approvedlights up Discord but awards zero points — there is no consumer to turn the event into score. A missed or half-processed approval therefore cannot be healed by replaying telemetry; it needs a one-off code backfill (see #502). - Two sources of truth drift. The event log can say "approved & announced" while the score lives in a separate direct write that may be silently absent — exactly the #502 state (no points, no Discord, telemetry never even emitted).
- Coupling / inconsistency. Feed-post scoring is welded into the judge orchestration instead of flowing through the event pipeline the way other score sources do.
Expected behavior
feed_post_approved should be picked up by a scoring consumer (alongside the DiscordConsumer) that applies the idempotent content:feed-post:<id> contribution. Then both effects are replayable from the event, single-sourced, and decoupled from the judge orchestration — and the #502 class of orphan heals itself on replay instead of needing a backfill.
Steps to reproduce / investigate
- Inspect
lib/app/feed/judge-post.ts— note the credit phase callscontributeFeedPostScoredirectly. - Grep consumers of
feed_post_approved: onlyservices/telemetry/lib/consumers/discord.ts. - Confirm
lib/app/process-score-batch.tshas no feed-post handling — it only recomputes totals from existing contributions. - Observe that re-firing the telemetry (e.g. via the credit backfill's
announceFeedPost) restores Discord but would not, on its own, restore points.
Affected components
lib/app/feed/judge-post.ts(credit phase / direct write)lib/app/feed/contribute-feed-post-score.tslib/app/feed/announce-feed-post.ts(telemetry emit)services/telemetry/lib/consumers/(where a scoring consumer would sit, next todiscord.ts)lib/app/process-score-batch.ts+event_queue(score pipeline)
Scope note
#502 fixes the immediate orphan: it makes the credit phase retriable and adds a one-off backfill for the affected post, deliberately keeping the existing direct-write award path to stay minimal. This issue tracks the deeper fix — move feed-post scoring onto a telemetry consumer so award + announce share the same replayable event.
Shipped
Click a lifecycle step above to view its details.
Sign in to post a ripple.