From f73e83a627cf32ef72accec2739db0c9004f3a8c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 18 Jul 2026 00:39:46 -0700 Subject: [PATCH] feat(ui): add gate-precision-vs-peer-median maintainer dashboard panel (#6481) Composes the already-shipped federated export/transport/import pipeline (#6478/#6479/#6480) into the local-vs-peer-median comparison the maintainer dashboard needs, gated on federatedIntelligence opt-in. --- .../federated-benchmark-card-model.ts | 37 +++ .../federated-benchmark-card.test.tsx | 63 +++++ .../app-panels/federated-benchmark-card.tsx | 78 +++++++ ...ntainer-panel-federated-benchmark.test.tsx | 94 ++++++++ .../site/app-panels/maintainer-panel.tsx | 7 + src/api/routes.ts | 14 ++ src/orb/federated-benchmark.ts | 69 ++++++ test/unit/federated-benchmark.test.ts | 218 ++++++++++++++++++ 8 files changed, 580 insertions(+) create mode 100644 apps/loopover-ui/src/components/site/app-panels/federated-benchmark-card-model.ts create mode 100644 apps/loopover-ui/src/components/site/app-panels/federated-benchmark-card.test.tsx create mode 100644 apps/loopover-ui/src/components/site/app-panels/federated-benchmark-card.tsx create mode 100644 apps/loopover-ui/src/components/site/app-panels/maintainer-panel-federated-benchmark.test.tsx create mode 100644 src/orb/federated-benchmark.ts create mode 100644 test/unit/federated-benchmark.test.ts diff --git a/apps/loopover-ui/src/components/site/app-panels/federated-benchmark-card-model.ts b/apps/loopover-ui/src/components/site/app-panels/federated-benchmark-card-model.ts new file mode 100644 index 0000000000..8f0cbf0388 --- /dev/null +++ b/apps/loopover-ui/src/components/site/app-panels/federated-benchmark-card-model.ts @@ -0,0 +1,37 @@ +// Federated benchmark card model (#6481). UI-side mirror of FederatedBenchmark from +// src/orb/federated-benchmark.ts, plus pure display helpers. + +export type MaintainerFederatedBenchmark = { + localMergePrecision: number | null; + peerMedianMergePrecision: number | null; + peerCount: number; + generatedAt: string; +}; + +/** mergePrecision is a 0-1 ratio (P(merged & not reverted | gate said merge)), never a 0-100 value. */ +export function formatMergePrecisionPct(value: number | null): string { + if (value == null) return "—"; + return `${Math.round(value * 1000) / 10}%`; +} + +/** True once at least one peer has contributed a numeric value to the median — distinct from "opted in but + * no peer data yet", which renders the panel's empty state rather than a comparison. */ +export function hasPeerBenchmark(benchmark: MaintainerFederatedBenchmark): boolean { + return benchmark.peerCount > 0 && benchmark.peerMedianMergePrecision !== null; +} + +/** Local precision minus peer median, in percentage points. Null whenever either side is unavailable — never + * a misleading zero. */ +export function precisionDeltaPct(benchmark: MaintainerFederatedBenchmark): number | null { + if (benchmark.localMergePrecision === null || benchmark.peerMedianMergePrecision === null) + return null; + return ( + Math.round((benchmark.localMergePrecision - benchmark.peerMedianMergePrecision) * 1000) / 10 + ); +} + +export function formatBenchmarkGeneratedAt(iso: string): string { + const parsed = Date.parse(iso); + if (!Number.isFinite(parsed)) return iso; + return new Date(parsed).toUTCString().slice(5, 22); +} diff --git a/apps/loopover-ui/src/components/site/app-panels/federated-benchmark-card.test.tsx b/apps/loopover-ui/src/components/site/app-panels/federated-benchmark-card.test.tsx new file mode 100644 index 0000000000..cda0c53b77 --- /dev/null +++ b/apps/loopover-ui/src/components/site/app-panels/federated-benchmark-card.test.tsx @@ -0,0 +1,63 @@ +import { render, screen } from "@testing-library/react"; +import { describe, expect, it } from "vitest"; + +import { FederatedBenchmarkCard } from "@/components/site/app-panels/federated-benchmark-card"; +import type { MaintainerFederatedBenchmark } from "@/components/site/app-panels/federated-benchmark-card-model"; + +function benchmark( + overrides: Partial = {}, +): MaintainerFederatedBenchmark { + return { + localMergePrecision: 0.9, + peerMedianMergePrecision: 0.8, + peerCount: 3, + generatedAt: "2026-07-16T00:00:00.000Z", + ...overrides, + }; +} + +describe("FederatedBenchmarkCard", () => { + it("renders both metrics, peer count, and a positive delta as 'ahead'", () => { + render(); + expect(screen.getByText("Gate precision vs peer median")).toBeTruthy(); + expect(screen.getByText("90%")).toBeTruthy(); + expect(screen.getByText("80%")).toBeTruthy(); + expect(screen.getByText("3 peers")).toBeTruthy(); + expect(screen.getByText("+10pp")).toBeTruthy(); + expect(screen.getByText("ahead")).toBeTruthy(); + expect(screen.getByText(/generated/i)).toBeTruthy(); + }); + + it("shows singular 'peer' for a peer count of 1", () => { + render( + , + ); + expect(screen.getByText("1 peer")).toBeTruthy(); + }); + + it("renders a negative delta as 'behind'", () => { + render(); + expect(screen.getByText("-20pp")).toBeTruthy(); + expect(screen.getByText("behind")).toBeTruthy(); + }); + + it("shows a dash delta with no tone pill when local precision is null despite real peer data", () => { + render(); + expect(screen.getByText("Your gate precision").parentElement?.textContent).toContain("—"); + expect(screen.queryByText("ahead")).toBeNull(); + expect(screen.queryByText("behind")).toBeNull(); + }); + + it("renders the empty state when opted in but no peer has contributed a value yet", () => { + render( + , + ); + expect(screen.getByText("No peer data yet")).toBeTruthy(); + expect(screen.getByText(/no trust-gated peer bundle has contributed/i)).toBeTruthy(); + expect(screen.queryByText("90%")).toBeNull(); + }); +}); diff --git a/apps/loopover-ui/src/components/site/app-panels/federated-benchmark-card.tsx b/apps/loopover-ui/src/components/site/app-panels/federated-benchmark-card.tsx new file mode 100644 index 0000000000..6e9b698daf --- /dev/null +++ b/apps/loopover-ui/src/components/site/app-panels/federated-benchmark-card.tsx @@ -0,0 +1,78 @@ +import { AnalyticsCardShell } from "@/components/site/app-panels/analytics-card-shell"; +import { StatusPill } from "@/components/site/control-primitives"; +import { + formatBenchmarkGeneratedAt, + formatMergePrecisionPct, + hasPeerBenchmark, + precisionDeltaPct, + type MaintainerFederatedBenchmark, +} from "@/components/site/app-panels/federated-benchmark-card-model"; + +/** Maintainer dashboard card (#6481): this instance's own gate precision vs the peer median computed from + * trust-gated federated bundles (#6478/#6479/#6480). The caller only mounts this when federation is enabled + * (data.qualityDashboard.federatedBenchmark is non-null) — an instance that hasn't opted in never sees this + * card at all, not a disabled version of it. No wallet/hotkey/reward/trust-score wording; "gate precision" is + * the same observable metric already shown elsewhere on this dashboard. */ +export function FederatedBenchmarkCard({ benchmark }: { benchmark: MaintainerFederatedBenchmark }) { + const hasPeers = hasPeerBenchmark(benchmark); + const delta = precisionDeltaPct(benchmark); + + return ( + + generated {formatBenchmarkGeneratedAt(benchmark.generatedAt)} + + } + > +
+ + + 0 ? "+" : ""}${delta}pp`} + tone={delta === null ? undefined : delta >= 0 ? "ready" : "warn"} + /> +
+
+ ); +} + +function Metric({ + label, + value, + detail, + tone, +}: { + label: string; + value: string; + detail?: string; + tone?: "ready" | "warn"; +}) { + return ( +
+
+ {label} +
+
+ {value} + {tone ? ( + {tone === "ready" ? "ahead" : "behind"} + ) : null} +
+ {detail ?
{detail}
: null} +
+ ); +} diff --git a/apps/loopover-ui/src/components/site/app-panels/maintainer-panel-federated-benchmark.test.tsx b/apps/loopover-ui/src/components/site/app-panels/maintainer-panel-federated-benchmark.test.tsx new file mode 100644 index 0000000000..06adb307c9 --- /dev/null +++ b/apps/loopover-ui/src/components/site/app-panels/maintainer-panel-federated-benchmark.test.tsx @@ -0,0 +1,94 @@ +import { render, screen } from "@testing-library/react"; +import { describe, expect, it, vi } from "vitest"; + +// A maintainer session + a path-aware data hook: the dashboard call resolves with a minimal payload; every +// other call stays in loading so sub-panels render harmlessly (mirrors maintainer-panel-slop.test.tsx). +const { useSession } = vi.hoisted(() => ({ useSession: vi.fn() })); +vi.mock("@/lib/api/session", () => ({ useSession: () => useSession() })); + +const baseDashboard = { + metrics: [], + health: [], + // Non-empty so MaintainerDashboardView's isEmpty check doesn't short-circuit to its own empty state + // before the federated-benchmark wiring under test ever renders (mirrors maintainer-panel-slop.test.tsx). + reviewability: [ + { + pr: "acme/widgets#7", + title: "Tidy things", + author: "alice", + bucket: "review-now", + reason: "cached open PR", + slop: null, + chatQaEnabled: false, + }, + ], + settingsPreview: { removed: [], added: [] }, + qualityDashboard: { + topContributors: [], + gateOutcomeBreakdown: { + windowDays: 30, + generatedAt: "2026-07-11T00:00:00.000Z", + counts: { autoMerged: 0, autoClosed: 0, held: 0 }, + total: 0, + rates: { autoMerged: null, autoClosed: null, held: null }, + summary: "No gate-outcome audit events in the last 30 day(s) for the scoped repos.", + }, + }, +}; + +vi.mock("@/lib/api/request", () => ({ apiFetch: vi.fn(async () => ({ ok: false })) })); +vi.mock("@/lib/api/origin", () => ({ getApiOrigin: () => "https://api.test" })); + +async function renderWithDashboard(federatedBenchmark: unknown) { + vi.resetModules(); + vi.doMock("@/lib/api/use-api-resource", () => ({ + useApiResource: (path: string) => + path.includes("maintainer-dashboard") + ? { + status: "ready", + data: { + ...baseDashboard, + qualityDashboard: { ...baseDashboard.qualityDashboard, federatedBenchmark }, + }, + reload: () => {}, + error: null, + } + : { status: "loading", data: null, reload: () => {}, error: null }, + })); + const { MaintainerPanel } = await import("@/components/site/app-panels/maintainer-panel"); + useSession.mockReturnValue({ + session: { login: "maint", roles: ["maintainer"] }, + hydrated: true, + }); + render(); +} + +describe("MaintainerPanel federated benchmark wiring (#6481)", () => { + it("renders no benchmark UI at all when the instance has not opted in (federatedBenchmark: null)", async () => { + await renderWithDashboard(null); + expect(screen.queryByText("Gate precision vs peer median")).toBeNull(); + }); + + it("renders the benchmark card when opted in, even with zero peer data yet", async () => { + await renderWithDashboard({ + localMergePrecision: 0.75, + peerMedianMergePrecision: null, + peerCount: 0, + generatedAt: "2026-07-16T00:00:00.000Z", + }); + expect(screen.getByText("Gate precision vs peer median")).toBeTruthy(); + expect(screen.getByText("No peer data yet")).toBeTruthy(); + }); + + it("renders a real comparison when opted in with peer data", async () => { + await renderWithDashboard({ + localMergePrecision: 0.9, + peerMedianMergePrecision: 0.8, + peerCount: 2, + generatedAt: "2026-07-16T00:00:00.000Z", + }); + expect(screen.getByText("Gate precision vs peer median")).toBeTruthy(); + expect(screen.getByText("90%")).toBeTruthy(); + expect(screen.getByText("2 peers")).toBeTruthy(); + }); +}); diff --git a/apps/loopover-ui/src/components/site/app-panels/maintainer-panel.tsx b/apps/loopover-ui/src/components/site/app-panels/maintainer-panel.tsx index f39ede8879..21f8262bff 100644 --- a/apps/loopover-ui/src/components/site/app-panels/maintainer-panel.tsx +++ b/apps/loopover-ui/src/components/site/app-panels/maintainer-panel.tsx @@ -38,6 +38,8 @@ import { } from "@/components/site/app-panels/queue-health-card"; import { SlopDuplicateTrendCard } from "@/components/site/app-panels/slop-duplicate-trend-card"; import type { MaintainerSlopDuplicateTrend } from "@/components/site/app-panels/slop-duplicate-trend-card-model"; +import { FederatedBenchmarkCard } from "@/components/site/app-panels/federated-benchmark-card"; +import type { MaintainerFederatedBenchmark } from "@/components/site/app-panels/federated-benchmark-card-model"; import { MaintainerSettings } from "@/components/site/app-panels/maintainer-settings"; import { OnboardingPreviewCard } from "@/components/site/app-panels/onboarding-preview-card"; import { CheckRunReadinessTable } from "@/components/site/check-run-readiness-table"; @@ -109,6 +111,7 @@ type MaintainerDashboard = { mcpToolUsage?: McpToolUsageSummary; queueHealth?: MaintainerQueueHealth; slopDuplicateTrend?: MaintainerSlopDuplicateTrend; + federatedBenchmark?: MaintainerFederatedBenchmark | null; }; }; @@ -455,6 +458,10 @@ function MaintainerDashboardView({ ) : null} + {data.qualityDashboard.federatedBenchmark ? ( + + ) : null} + diff --git a/src/api/routes.ts b/src/api/routes.ts index 89a97225be..1f79faad28 100644 --- a/src/api/routes.ts +++ b/src/api/routes.ts @@ -305,6 +305,8 @@ import { loadPublicReuseRateTrend } from "../services/public-reuse-rate-trend"; import { loadPublicReviewVolumeTrend } from "../services/public-review-volume-trend"; import { buildMaintainerQualityDashboard, isMaintainerQualityDataStale } from "../services/maintainer-quality-dashboard"; import { buildMaintainerSlopDuplicateTrend, SLOP_DUPLICATE_TREND_SNAPSHOT_LIMIT } from "../services/maintainer-slop-duplicate-trend"; +import { buildFederatedBenchmark } from "../orb/federated-benchmark"; +import { resolveLoopOverSelfRepoFullName } from "../config/loopover-repo-focus-manifest"; import { buildGateOutcomeBreakdown, GATE_OUTCOME_BREAKDOWN_WINDOW_DAYS } from "../services/gate-outcome-breakdown"; import { MAX_LOCAL_SCORER_WARNING_CHARS, MAX_LOCAL_SCORER_WARNING_COUNT } from "../signals/local-scorer-diagnostics"; import { compileFocusManifestPolicy, MAX_FOCUS_MANIFEST_BYTES, resolveEffectiveSettings } from "../signals/focus-manifest"; @@ -1648,6 +1650,17 @@ export function createApp() { stale: qualityStale, nowMs: Date.parse(generatedAt), }); + // Federated benchmark (#6481): "your gate precision vs peer median". Reads the opt-in from the loopover + // self-repo's manifest (mirrors prReconciliation/publicStats/etc.'s fleet-wide override lookup) rather + // than any of the maintainer's own repos — federatedIntelligence is operator-level, not per-repo. Bounded + // to a single, short-timeout attempt so an unreachable or slow collector degrades the panel to its + // existing empty state instead of holding up the whole dashboard load. + const federatedIntelligenceManifest = await loadRepoFocusManifest(c.env, resolveLoopOverSelfRepoFullName(c.env)); + const federatedBenchmark = await buildFederatedBenchmark(federatedIntelligenceManifest, c.env.DB, { + now: Date.parse(generatedAt), + timeoutMs: 5_000, + maxAttempts: 1, + }); const qualityDashboard = { ...buildMaintainerQualityDashboard({ repos: qualityRepoInputs, @@ -1656,6 +1669,7 @@ export function createApp() { repoTotal: repositories.length, }), slopDuplicateTrend, + federatedBenchmark, }; const gateOutcomeSinceIso = new Date(Date.parse(generatedAt) - GATE_OUTCOME_BREAKDOWN_WINDOW_DAYS * 24 * 60 * 60 * 1000).toISOString(); const gateOutcomeRollups = await listGateOutcomeAuditEventRollups(c.env, { diff --git a/src/orb/federated-benchmark.ts b/src/orb/federated-benchmark.ts new file mode 100644 index 0000000000..c5d7db0629 --- /dev/null +++ b/src/orb/federated-benchmark.ts @@ -0,0 +1,69 @@ +// LoopOver federated fleet intelligence (#1970) — the dashboard benchmark (#6481): "your gate precision vs +// peer median". Composes the three already-shipped pipeline stages — export (#6478, federated-bundle.ts), +// transport (#6479, federated-collector.ts), and trust-gated import (#6480, federated-import.ts) — into the +// one comparison the maintainer dashboard renders. No new storage, no new network primitive: this module is +// pure composition over functions that already exist and already fail safe on their own. +// +// FAIL-SAFE BY COMPOSITION, not by a wrapping try/catch: buildFederatedBundle degrades to null on any error, +// pullPeerBundles degrades to [] on any error or when not opted in, and importPeerBundles is a pure function +// with no I/O. None of the three can throw, so this module doesn't need to catch anything either — wrapping +// it in another try/catch would only hide which stage actually failed. +import { buildFederatedBundle, isFederatedIntelligenceEnabled } from "./federated-bundle"; +import { importPeerBundles } from "./federated-import"; +import { pullPeerBundles, type CollectorOpts } from "./federated-collector"; +import { percentile } from "./analytics"; +import type { FocusManifest } from "../signals/focus-manifest"; + +export interface FederatedBenchmark { + /** This instance's own P(merged & not reverted | gate said merge), from buildFederatedBundle. Null below + * MIN_DECIDED, exactly like the exported bundle field it reuses. */ + localMergePrecision: number | null; + /** Median mergePrecision across every accepted (trust-gated) peer bundle that itself cleared MIN_DECIDED. + * Null when no peer contributed a numeric value yet — an empty-state condition, not an error. */ + peerMedianMergePrecision: number | null; + /** How many peers actually contributed to the median above (i.e. passed trust-gating AND had a non-null + * mergePrecision) — NOT the raw count of bundles pulled or accepted, which may include peers still below + * their own MIN_DECIDED threshold. */ + peerCount: number; + generatedAt: string; +} + +/** + * Build the local-vs-peer-median benchmark for the maintainer dashboard. + * + * Returns null — touching nothing beyond the opt-in check — when federated intelligence is not enabled for + * this deployment (`federatedIntelligence.enabled` off in the loopover self-repo's manifest). This is the + * "an instance that hasn't opted in sees no new UI, not an empty/disabled version of it" gate #6481 requires; + * the caller renders no panel at all on a null result, distinct from a real object with peerCount: 0 (opted + * in, no peer data yet — an empty state, not an error). + */ +export async function buildFederatedBenchmark( + manifest: Pick | null | undefined, + db: D1Database, + opts: { now?: number; windowDays?: number } & CollectorOpts = {}, +): Promise { + if (!isFederatedIntelligenceEnabled(manifest)) return null; + + const now = Number.isFinite(opts.now) ? (opts.now as number) : Date.now(); + // exactOptionalPropertyTypes forbids `windowDays: undefined` — only include the key when a real value was + // passed, so an omitted opts.windowDays falls through to buildFederatedBundle's own default instead of + // being overridden with an explicit undefined. + const localBundle = await buildFederatedBundle(manifest, db, opts.windowDays === undefined ? { now } : { now, windowDays: opts.windowDays }); + + const peerBundles = await pullPeerBundles(manifest, opts); + const { accepted } = importPeerBundles(manifest, peerBundles); + // MEDIAN, NOT MEAN (mirrors analytics.ts's own fleet aggregation, see federated-import.ts's header comment): + // a bounded number of outliers cannot drag a median arbitrarily, so re-deriving a mean here would quietly + // weaken the same poisoning-resistance property the import side already relies on holding by construction. + const peerMergePrecisions = accepted + .map((bundle) => bundle.mergePrecision) + .filter((value): value is number => value !== null) + .sort((a, b) => a - b); + + return { + localMergePrecision: localBundle?.mergePrecision ?? null, + peerMedianMergePrecision: percentile(peerMergePrecisions, 50), + peerCount: peerMergePrecisions.length, + generatedAt: new Date(now).toISOString(), + }; +} diff --git a/test/unit/federated-benchmark.test.ts b/test/unit/federated-benchmark.test.ts new file mode 100644 index 0000000000..68d822c1fe --- /dev/null +++ b/test/unit/federated-benchmark.test.ts @@ -0,0 +1,218 @@ +import { DatabaseSync } from "node:sqlite"; +import { createHmac } from "node:crypto"; +import { describe, expect, it, vi } from "vitest"; +import { createD1Adapter, nodeSqliteDriver } from "../../src/selfhost/d1-adapter"; +import { canonicalizeFederatedBundleBody, FEDERATED_BUNDLE_SCHEMA_VERSION, type FederatedSignalBundle } from "../../src/orb/federated-bundle"; +import { buildFederatedBenchmark } from "../../src/orb/federated-benchmark"; +import type { FederatedCollectorMode, FocusManifest } from "../../src/signals/focus-manifest"; + +const URL_OK = "https://collector.example.org/v1/federated"; +// Fake 64-hex keys — the shape generateAnonSecret produces. Not secrets: locally-invented test fixtures. +const PEER_KEY_A = "a".repeat(64); +const PEER_KEY_B = "b".repeat(64); +const UNTRUSTED_KEY = "c".repeat(64); +const NOW = Date.parse("2026-07-16T00:00:00Z"); + +/** In-memory DB with the tables buildFederatedBundle reads (mirrors federated-bundle.test.ts's makeDb). */ +function makeDb(): D1Database { + const driver = nodeSqliteDriver(new DatabaseSync(":memory:") as never); + driver.exec(` + CREATE TABLE review_audit ( + id TEXT PRIMARY KEY NOT NULL, project TEXT NOT NULL, target_id TEXT NOT NULL, + event_type TEXT NOT NULL DEFAULT 'gate_decision', decision TEXT, + source TEXT NOT NULL DEFAULT 'gittensory-native', head_sha TEXT, summary TEXT, + created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ','now')) + ); + CREATE TABLE system_flags ( + key TEXT PRIMARY KEY, value TEXT, + updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ','now')) + ); + `); + return createD1Adapter(driver); +} + +/** A db that fails the test if it is touched at all — proves the opted-out path reads nothing. */ +function untouchableDb(): D1Database { + return new Proxy({} as D1Database, { + get() { + throw new Error("opted-out build must not touch the database"); + }, + }); +} + +let seq = 0; +async function resolved( + db: D1Database, + pr: number, + o: { verdict?: string; outcome?: string; reversal?: "reversal_reverted" | "reversal_reopened" } = {}, +): Promise { + const insert = (type: string, decision: string | null, at: string) => + db + .prepare( + `INSERT INTO review_audit (id, project, target_id, event_type, decision, source, summary, created_at) VALUES (?, ?, ?, ?, ?, 'gittensory-native', NULL, ?)`, + ) + .bind(`b${seq++}`, "owner/repo", `owner/repo#${pr}`, type, decision, at) + .run(); + await insert("gate_decision", o.verdict ?? "merge", "2026-07-10T10:00:00Z"); + await insert("pr_outcome", o.outcome ?? "merged", "2026-07-10T12:00:00Z"); + if (o.reversal) await insert(o.reversal, null, "2026-07-10T13:00:00Z"); +} + +function manifest( + o: { + enabled?: boolean; + peerKeys?: string[]; + collectorUrl?: string | null; + collectorMode?: FederatedCollectorMode | null; + } = {}, +): Pick { + return { + federatedIntelligence: { + present: true, + enabled: o.enabled ?? true, + peerKeys: o.peerKeys ?? [PEER_KEY_A], + collectorUrl: o.collectorUrl === undefined ? URL_OK : o.collectorUrl, + collectorMode: o.collectorMode ?? null, + }, + }; +} + +const peerBody = (over: Partial = {}) => ({ + schemaVersion: FEDERATED_BUNDLE_SCHEMA_VERSION, + instanceId: "peerinstance1234", + generatedAt: "2026-07-15T00:00:00.000Z", + windowDays: 90, + decided: 40, + mergePrecision: 0.8, + closePrecision: 0.7, + fpRate: 0.1, + fnRate: 0.2, + reversalRate: 0.05, + cycleP50Ms: 1000, + cycleP95Ms: 5000, + slopRate: 0.1, + copycatRate: 0.02, + ...over, +}); + +/** Sign a peer bundle the way a real peer's export side does, so a real signature verifies against peerKeys. */ +function signedWith(key: string, over: Partial = {}): FederatedSignalBundle { + const payload = peerBody(over); + const signature = createHmac("sha256", key).update(canonicalizeFederatedBundleBody(payload)).digest("hex"); + return { ...payload, signature }; +} + +function fetchReturning(bundles: FederatedSignalBundle[]): typeof fetch { + return (async () => Response.json(bundles)) as unknown as typeof fetch; +} + +describe("buildFederatedBenchmark() — not opted in", () => { + it("returns null and touches neither the database nor the network for an absent/false manifest", async () => { + const fetchSpy = vi.fn(); + expect(await buildFederatedBenchmark(manifest({ enabled: false }), untouchableDb(), { fetchFn: fetchSpy })).toBeNull(); + expect(fetchSpy).not.toHaveBeenCalled(); + }); + + it("returns null for a null manifest", async () => { + expect(await buildFederatedBenchmark(null, untouchableDb())).toBeNull(); + }); +}); + +describe("buildFederatedBenchmark() — opted in, no peer data yet", () => { + it("returns local precision with peerCount 0 and a null median when no collector is configured", async () => { + const db = makeDb(); + for (let pr = 1; pr <= 5; pr++) await resolved(db, pr); + + const result = await buildFederatedBenchmark(manifest({ collectorUrl: null }), db, { now: NOW }); + + expect(result).not.toBeNull(); + expect(result?.localMergePrecision).toBe(1); + expect(result?.peerMedianMergePrecision).toBeNull(); + expect(result?.peerCount).toBe(0); + expect(result?.generatedAt).toBe("2026-07-16T00:00:00.000Z"); + }); + + it("returns peerCount 0 when every pulled bundle is rejected by trust-gating (untrusted key)", async () => { + const db = makeDb(); + for (let pr = 1; pr <= 5; pr++) await resolved(db, pr); + const fetchFn = fetchReturning([signedWith(UNTRUSTED_KEY)]); + + const result = await buildFederatedBenchmark(manifest({ peerKeys: [PEER_KEY_A] }), db, { now: NOW, fetchFn }); + + expect(result?.peerCount).toBe(0); + expect(result?.peerMedianMergePrecision).toBeNull(); + }); + + it("excludes an accepted peer bundle whose own mergePrecision is null (peer below its own MIN_DECIDED)", async () => { + const db = makeDb(); + for (let pr = 1; pr <= 5; pr++) await resolved(db, pr); + const fetchFn = fetchReturning([signedWith(PEER_KEY_A, { mergePrecision: null })]); + + const result = await buildFederatedBenchmark(manifest({ peerKeys: [PEER_KEY_A] }), db, { now: NOW, fetchFn }); + + expect(result?.peerCount).toBe(0); + expect(result?.peerMedianMergePrecision).toBeNull(); + }); +}); + +describe("buildFederatedBenchmark() — opted in, local precision below MIN_DECIDED", () => { + it("reports a null local precision but still computes the peer median", async () => { + const db = makeDb(); + await resolved(db, 1); // only 1 decided PR, below MIN_DECIDED (5) + const fetchFn = fetchReturning([signedWith(PEER_KEY_A, { mergePrecision: 0.6 })]); + + const result = await buildFederatedBenchmark(manifest({ peerKeys: [PEER_KEY_A] }), db, { now: NOW, fetchFn }); + + expect(result?.localMergePrecision).toBeNull(); + expect(result?.peerMedianMergePrecision).toBe(0.6); + expect(result?.peerCount).toBe(1); + }); +}); + +describe("buildFederatedBenchmark() — opted in, real peer comparison", () => { + it("computes the median (not the mean) across every accepted peer, ignoring an untrusted one mixed in", async () => { + const db = makeDb(); + for (let pr = 1; pr <= 5; pr++) await resolved(db, pr); + const fetchFn = fetchReturning([ + signedWith(PEER_KEY_A, { instanceId: "peer-a", mergePrecision: 0.5 }), + signedWith(PEER_KEY_B, { instanceId: "peer-b", mergePrecision: 0.9 }), + signedWith(UNTRUSTED_KEY, { instanceId: "peer-attacker", mergePrecision: 0.01 }), + ]); + + const result = await buildFederatedBenchmark(manifest({ peerKeys: [PEER_KEY_A, PEER_KEY_B] }), db, { now: NOW, fetchFn }); + + // Median of [0.5, 0.9] (the untrusted 0.01 is rejected, not merely a low outlier) is 0.5 under this + // module's nearest-rank percentile(50) (analytics.ts: idx = ceil(0.5*2)-1 = 0) — pinning the real + // cross-module contract, not a re-derivation. + expect(result?.peerCount).toBe(2); + expect(result?.peerMedianMergePrecision).toBe(0.5); + expect(result?.localMergePrecision).toBe(1); + }); + + it("honors an explicit windowDays override, narrowing the local calibration window", async () => { + const db = makeDb(); + // Resolved 30 days before `now` — inside the default 90-day window but outside a 7-day override. + for (let pr = 1; pr <= 5; pr++) { + await resolved(db, pr, {}); + await db.prepare("UPDATE review_audit SET created_at = '2026-06-16T12:00:00Z' WHERE target_id = ?").bind(`owner/repo#${pr}`).run(); + } + + const wide = await buildFederatedBenchmark(manifest({ collectorUrl: null }), db, { now: NOW, windowDays: 90 }); + const narrow = await buildFederatedBenchmark(manifest({ collectorUrl: null }), db, { now: NOW, windowDays: 7 }); + + expect(wide?.localMergePrecision).toBe(1); + expect(narrow?.localMergePrecision).toBeNull(); + }); + + it("uses Date.now() for generatedAt when opts.now is not provided", async () => { + vi.useFakeTimers(); + vi.setSystemTime(new Date(NOW)); + try { + const db = makeDb(); + const result = await buildFederatedBenchmark(manifest({ collectorUrl: null }), db, {}); + expect(result?.generatedAt).toBe("2026-07-16T00:00:00.000Z"); + } finally { + vi.useRealTimers(); + } + }); +});