Files
bitgate/tests/feed-ranking.test.js
T
thePR0M3TH3ANandClaude Opus 4.8 1abba23df3 docs: worked example of the ranking dimension as a feed multiplier
The `ranking` dimension is the soft one — "still show this, but lower" — and
integrators reinvented turning it into a feed score. Show it instead.

Add examples/feed-ranking.mjs (runnable): it reads decision.ranking
({ effect: "normal" | "downrank", weight }) and turns it into a multiplier
on the app's own feed score (base ** weight for a downrank). Two authors
that led on base score get downranked by trusted spam reports and drop below
a clean lower-base author -- without being hidden, since visibility is a
separate dimension (capped at "restrict" on a feed). The multiplier lives in
the example, not the engine: core ships no such numbers on purpose.

Pinned by tests/feed-ranking.test.js (downrank never hides on a feed; a
high-base downranked author reorders below a clean one; a clean author is
untouched at x1). README headless section points to it.

Addresses the "BitGate feeding a feed ranking" item of the Tessera feedback
(docs/TODO.md) -- the last open BitGate item.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01BXQCvJPA6sVSQ5j8uBd9bB
2026-08-11 20:43:35 -04:00

62 lines
2.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Pins examples/feed-ranking.mjs: the `ranking` dimension, applied as a score
// multiplier, reorders a downranked author below a clean one without hiding it.
import { describe, expect, it } from "vitest";
import { SOCIAL_POLICY, createAuthorityState, createSnapshot, evaluateTarget } from "@bitgate/core";
const ROOT = "a1".repeat(32);
const NOW = 1_750_000_000;
const TRUSTED = Array.from({ length: 16 }, (_, i) => (i + 1).toString(16).padStart(2, "0").repeat(32));
const authority = createAuthorityState({ root: ROOT });
const snapshotFor = (pubkey, category, count) =>
createSnapshot({
authority,
trust: { contacts: new Set(TRUSTED) },
reports: count
? new Map([
[
`user:${pubkey}`,
TRUSTED.slice(0, count).map((reporter) => ({ reporter, category, createdAt: NOW - 100 })),
],
])
: new Map(),
});
const rankingOf = (pubkey, category, count) =>
evaluateTarget({ type: "user", pubkey }, snapshotFor(pubkey, category, count), {
surface: "feed",
policyProfile: "feed",
policy: SOCIAL_POLICY,
now: NOW,
});
// The same multiplier the example documents.
const multiplier = (ranking, base = 0.5) =>
ranking?.effect === "downrank" ? base ** Math.max(1, ranking.weight) : 1;
describe("ranking dimension as a feed multiplier", () => {
it("downranks a reported author but never hides them on a feed", () => {
const decision = rankingOf("d2".repeat(32), "spam", 4);
expect(decision.ranking.effect).toBe("downrank");
expect(decision.ranking.weight).toBeGreaterThanOrEqual(1);
// Ranking is the soft dimension: visibility stays out of hide/deny on a feed.
expect(["allow", "warn", "restrict"]).toContain(decision.visibility.effect);
});
it("reorders a high-base downranked author below a clean lower-base one", () => {
const clean = { base: 0.7, ranking: rankingOf("d1".repeat(32), "spam", 0).ranking };
const reported = { base: 0.95, ranking: rankingOf("d2".repeat(32), "spam", 4).ranking };
const cleanFinal = clean.base * multiplier(clean.ranking);
const reportedFinal = reported.base * multiplier(reported.ranking);
expect(reported.base).toBeGreaterThan(clean.base); // led on base score
expect(reportedFinal).toBeLessThan(cleanFinal); // ...but sinks after ranking
});
it("leaves a clean author's score untouched (×1)", () => {
expect(multiplier(rankingOf("d1".repeat(32), "spam", 0).ranking)).toBe(1);
});
});