feat(votes): emit bridgedStats on records via debounced refresher master
Lemmy vote counts now ride the post/comment records themselves as an optional bridgedStats {upvotes, downvotes, asOf} field, replacing the getVoteAggregates XRPC as the primary AppView surface (the endpoint stays as legacy/debug). Votes still never become records (locked decision 7): a debounced refresher sweeps vote_aggregates rows whose updated_at passed the new stats_emitted_at watermark and stamps the current counts onto the materialized record, so any firehose consumer gets Lemmy-side scores with no bridge-specific API coupling. Changes: - internal/votes/refresher.go: watermark sweeper (STATS_REFRESH_INTERVAL / STATS_REFRESH_BATCH), batch-bounded, oldest-dirty first; watermark advances to the updated_at value read (never now()), guarded against concurrent recomputes; permanent skips (record gone, tombstoned repo, validation failure) advance with logs, transient errors stay dirty - internal/materialize/bridgedstats.go: stamp path — counts-unchanged is a NoOp without commit (no asOf-only firehose churn); asOf rendered at microsecond precision - repo.PutRecordCAS: expected-previous-CID precondition checked inside the commit locks (ErrPreconditionFailed + bounded retry) so a stats stamp can never revert a concurrent edit or resurrect a deleted record; mapping deleted_at re-checked in-tx - carry-forward: Lemmy edit rebuilds preserve bridgedStats AND reply strongRefs verbatim (Coves treats reply CIDs as immutable; stats stamps churn the parent post's CID) - SeedAggregates stores the baseline net of live events, clamped at zero — a backfill redo converges to Lemmy's truth instead of compounding a double-count (both residual races documented) - recomputeAggregate uses clock_timestamp() so updated_at is per-subject monotonic under concurrent voters (the watermark cannot strand a committed vote) - errors.ErrRecordGone distinguishes "record deleted" from other NotFounds (a missing signing key stays transient, never skipped) - migration 014: stats_emitted_at watermark + partial dirty-rows index - lexicons: post/comment gain #bridgedStats (synced from Coves) - e2e: refresher runs at 2s in the stack; restart-dedup and unsubscribe negative assertions exclude only updates equal to the prior record modulo bridgedStats; positive assertion on emitted counts; full suite green (ok tidepool/tests/e2e 237.6s) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>