Skip to content

Discovery ranking snapshot contract — final unshipped v1

Implements server review findings 2 and 5. The feature version remains discovery-features-v1: no production data has been collected under an earlier iteration. This is an implementation handoff, not an independent review of these fixes.

DiscoveryRankSnapshot keeps its existing fields and adds two optional fields:

ts
deliveryFeatures?: Record<string, number>;
deliveryMetadata?: {
  creatorId: string;
  topic: string;
  languageGroupId: string | null;
};

Ranking features now has 136 numeric fields, with a fixed set of names across cold/warm/mature policies and head/tail/exploration positions. The complete names and a real generated snapshot are in discovery-snapshot-fixture.json. The original model vector and score_* components remain; additional fields record raw engagement/retention inputs, globals, exposure/exclusion flags, source membership, cold retention score/jitter, quota/semantic constraints and relaxation pass, listwise counts/penalties, and exploration eligibility/rotation/selection. Score components are captured during the actual scoring call, eliminating the previous second model prediction.

Positions are zero-based. rank_input_position is the filtered batch input position; rank_pre_diversity_position is the score-sorted position immediately before listwise/cold composition. rank_post_diversity_position and rank_post_exploration_position describe the subsequent stages. Existing rankPosition remains the final position within that ranked batch. Cold tail rows have cold_window_applied=0; listwise tail rows have listwise_applied=0; inactive stages have zero values plus an explicit applied flag. No raw embeddings or story/message content enter snapshots.

The engagement loader exposes optional WorldEngagementStat.updatedAt, serialized as ISO UTC from the source rollup's existing updated_at column. stats_updated_at_ms is that timestamp; stats_age_at_rank_seconds is (rankedAt - updatedAt) / 1000. stats_updated_at_known=0 means the corresponding zero timestamp/age is a placeholder, not freshness evidence. Missing rows and older cached snapshots remain explicitly unknown. stats_present distinguishes those cases. rankedAt remains the ranking evaluation time, never a substitute rollup time. Global aggregate values are captured numerically; the per-world timestamp is not a claim that every row contributing to globals shares that timestamp.

Delivery deliveryFeatures has 19 numeric fields. It captures the original queue index before the hydration filter (queue_position), the candidate's index in the remaining cost-comparison pool (pre_diversity_position), absolute stream position (post_diversity_position), page-relative position, creator/topic repeat counts, consecutive-creator flag, all three weights and actual total cost. It also records queue/pool size, counts removed at hydration, and current history/seen-set sizes. eligible_count denotes the remaining pool after the hydration-time eligibility filter; a sibling made seen by an earlier selection is still skipped by the existing final group check. Exclusion counts describe that hydration, while history/seen counts describe the selection itself.

Delivery metadata is copied from the card used for the selection, including an explicit null group. Pending queued snapshots contain ranking inputs only. Selecting a card adds its delivery fields without changing its original ranking inputs/time. The existing session CAS stores both stages; retries do not recompute either. Controller integration should prefer delivery metadata on first persistence and the persisted canonical group on retries, preserving null rather than treating it as a missing value.

Keyset catalog replenishment now supplies candidateSource: ["catalog"], source_catalog=1 and source_popular_recent=0. Genuine popular retrieval remains separately labeled. The feed no longer fabricates catalog provenance when a source is absent.

Verification: 89/89 focused tests passed across discovery-feed.test.ts, discovery-pagination.test.ts, discovery-session.test.ts, recommendations.test.ts and engagement.test.ts. Tests include the original retention/order mismatch, actual source query, model-call parity, policy output parity, cold semantic relaxation, exploration, source timestamps, exact delivery costs and a real full-schema feed with changed stats/group/topic between pages and retries. Scoped TypeScript compilation of owned source/test roots and their dependencies finished with zero diagnostics. git diff --check passed for edited tracked files. No global build/OSS or production verification was run.

The fixture snapshot alone serializes to 4,398 bytes with UUID metadata; this is an example, not a worst-case capacity bound. Copernicus has the fixture and final field counts for measurement-enabled Redis sizing. No measurement/outcome/receipt/schema/route or session implementation files were edited by this fix worker; the engagement edit is limited to timestamp exposure. No commit/push.