Skip to content

Commit e4c8364

Browse files
RealDiligentRealDiligent
authored andcommitted
docs+ui: add 'Verify this review' walkthrough and measured per-rule accuracy on the fairness report
Adds the public reproducibility walkthrough doc (export corpus snapshot, verify checksum, replay scorer, compare against published numbers, with an honest attestation-boundary callout) and renders the measured accuracy per rule section on the fairness report from the public stats rulePrecision block: decided cases, precision with the insufficient-data null state (never 0%), reversal counts, and the corpus-checksum freeze point linking to the walkthrough. Closes #8231
1 parent 7a8b9b6 commit e4c8364

7 files changed

Lines changed: 244 additions & 0 deletions

File tree

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
---
2+
title: Verify this review
3+
description: Re-run LoopOver's published backtest corpus yourself — download a checksummed snapshot, replay the same scorer, and compare against the published numbers.
4+
eyebrow: Core concepts
5+
---
6+
7+
## Why this page exists
8+
9+
LoopOver publishes measured per-rule precision on the [fairness report](/fairness). Numbers on a
10+
website only build trust if a skeptic can check them without asking anyone's permission. This
11+
page is the end-to-end walkthrough: export the same corpus snapshot the numbers come from, verify
12+
its checksum, replay the same scorer over it, and compare what you get against what is published.
13+
14+
Everything below runs read-only against a database export and pure functions from
15+
`@loopover/engine`. Nothing posts anywhere, nothing needs an API key.
16+
17+
## 1. Export the corpus snapshot
18+
19+
Every rule's fired/override history exports as a versioned, checksummed JSON snapshot
20+
([backtest & calibration](/docs/backtest-calibration) explains how that history is recorded):
21+
22+
```bash
23+
npx tsx scripts/backtest-corpus-export.ts --rule-id linked_issue_scope_mismatch --output corpus.json --remote
24+
```
25+
26+
On a self-host deployment, point the same CLI at your own Postgres instead:
27+
28+
```bash
29+
npx tsx scripts/backtest-corpus-export.ts --rule-id linked_issue_scope_mismatch --output corpus.json --pg "$DATABASE_URL"
30+
```
31+
32+
The snapshot's `checksum` field is a SHA-256 over the canonicalized cases (keys sorted, so
33+
property order can never change the hash). The fairness report's *reproducibility freeze point*
34+
shows the checksum of the corpus behind the latest persisted backtest run — an export of the same
35+
window reproduces the same checksum, byte for byte.
36+
37+
## 2. Verify the checksum
38+
39+
The manifest is self-verifying: recompute the hash over its own `cases` array and compare it to
40+
the recorded `checksum`. The canonicalization lives in `scripts/backtest-corpus-export-core.ts`
41+
(`buildBacktestCorpusManifest`), so the check is one short script:
42+
43+
```bash
44+
node --experimental-strip-types -e '
45+
import { readFileSync } from "node:fs";
46+
import { buildBacktestCorpusManifest } from "./scripts/backtest-corpus-export-core.ts";
47+
const saved = JSON.parse(readFileSync("corpus.json", "utf8"));
48+
const recomputed = buildBacktestCorpusManifest(saved.ruleId, saved.cases);
49+
console.log(recomputed.checksum === saved.checksum ? "checksum OK" : "CHECKSUM MISMATCH");
50+
'
51+
```
52+
53+
## 3. Replay the scorer
54+
55+
The published precision comes from the same pure functions any Node script can import:
56+
`scoreBacktest` replays a classifier over the labeled cases, and `compareBacktestScores` applies
57+
the Pareto-floor verdict between two scores. Replaying the shipped confidence floor over your
58+
verified snapshot:
59+
60+
```bash
61+
node --experimental-strip-types -e '
62+
import { readFileSync } from "node:fs";
63+
import { buildConfidenceThresholdClassifier, scoreBacktest } from "@loopover/engine";
64+
const saved = JSON.parse(readFileSync("corpus.json", "utf8"));
65+
const report = scoreBacktest(saved.ruleId, saved.cases, buildConfidenceThresholdClassifier(0.5));
66+
console.log(report);
67+
'
68+
```
69+
70+
- **"Reversed" is the positive class** — a prediction of `reversed` says the rule's original
71+
firing was wrong, and it is scored against what a human actually decided.
72+
- **`null` is never `0`.** Precision and recall stay `null` below the decided-sample floor;
73+
the fairness report renders that as *insufficient data*, never as a zero.
74+
75+
## 4. Compare against the published numbers
76+
77+
The [fairness report](/fairness) renders each rule's decided-case count and measured precision
78+
from the public stats endpoint (`/v1/public/stats`, the `rulePrecision` block). The aggregated
79+
run history is also readable directly:
80+
81+
```bash
82+
npx tsx scripts/backtest-track-record.ts --db loopover --remote
83+
```
84+
85+
Your replayed `confirmed / decided` for a rule should match the published precision for the same
86+
window; the freeze-point checksum ties the published numbers to the exact corpus you just
87+
verified.
88+
89+
## What this proves — and what it does not
90+
91+
<Callout variant="note">
92+
**Proved:** the published scores are real computations over a real, checksummed, replayable
93+
corpus — not hand-entered numbers. Anyone can independently reproduce them from the snapshot.
94+
95+
**Not proved:** that the live gate *ran this exact code* when it made its decisions. Verifying
96+
the runtime itself is an attestation problem — a trusted-execution boundary, not a replay
97+
boundary — and is tracked as its own explicitly-scoped decision in
98+
[#8136](https://github.com/JSONbored/loopover/issues/8136) and
99+
[#8137](https://github.com/JSONbored/loopover/issues/8137). This walkthrough is honest about
100+
stopping at the reproducibility line rather than implying the stronger guarantee.
101+
</Callout>

apps/loopover-ui/src/components/site/command-palette.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ const DEFAULT_ITEMS: PaletteItem[] = [
6262
{ label: "Upstream drift", to: "/docs/upstream-drift", group: "Docs" },
6363
{ label: "AI summaries policy", to: "/docs/ai-summaries", group: "Docs" },
6464
{ label: "Backtest & calibration", to: "/docs/backtest-calibration", group: "Docs" },
65+
{ label: "Verify this review", to: "/docs/verify-this-review", group: "Docs" },
6566
{ label: "Privacy & security", to: "/docs/privacy-security", group: "Docs" },
6667
{ label: "Troubleshooting", to: "/docs/troubleshooting", group: "Docs" },
6768
{ label: "API reference", to: "/api", group: "Reference" },

apps/loopover-ui/src/components/site/docs-nav.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ export const docsNav: DocsGroup[] = [
101101
{ to: "/docs/scoreability", label: "Scoreability" },
102102
{ to: "/docs/upstream-drift", label: "Upstream drift" },
103103
{ to: "/docs/backtest-calibration", label: "Backtest & calibration" },
104+
{ to: "/docs/verify-this-review", label: "Verify this review" },
104105
],
105106
},
106107
{

apps/loopover-ui/src/components/site/fairness-report-page.test.tsx

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,24 @@ vi.mock("@/lib/api/request", () => ({
1111
}));
1212
vi.mock("@/lib/api/origin", () => ({ getApiOrigin: () => "https://api.example.test" }));
1313

14+
// Mirrors proof-of-power-stats.test.tsx: <Link> needs a real router context; render a plain <a>.
15+
vi.mock("@tanstack/react-router", () => ({
16+
Link: ({
17+
to,
18+
children,
19+
...props
20+
}: {
21+
to: string;
22+
children: ReactNode;
23+
className?: string;
24+
"aria-label"?: string;
25+
}) => (
26+
<a href={to} {...props}>
27+
{children}
28+
</a>
29+
),
30+
}));
31+
1432
import { FairnessReportPage } from "./fairness-report-page";
1533
import type { PublicStats } from "./proof-of-power-stats-model";
1634

@@ -51,6 +69,47 @@ describe("FairnessReportPage (#fairness-analytics)", () => {
5169
apiFetch.mockReset();
5270
});
5371

72+
it("renders the measured per-rule precision table with the insufficient-data null state — never 0% (#8231)", async () => {
73+
apiFetch.mockResolvedValue({
74+
ok: true,
75+
data: {
76+
...FIXTURE,
77+
rulePrecision: {
78+
windowDays: 90,
79+
rules: [
80+
{ ruleId: "linked_issue_scope_mismatch", decided: 42, precision: 0.952 },
81+
{ ruleId: "slop_gate_score", decided: 3, precision: null },
82+
],
83+
reversals: { reopened: 2, reverted: 1, superseded: 0 },
84+
latestBacktestRun: { corpusChecksum: "a".repeat(64), at: "2026-07-22T00:00:00.000Z" },
85+
},
86+
},
87+
durationMs: 10,
88+
});
89+
renderWithClient(<FairnessReportPage />);
90+
91+
await waitFor(() => expect(screen.getByText("Measured accuracy per rule")).toBeTruthy());
92+
expect(screen.getByText("linked_issue_scope_mismatch")).toBeTruthy();
93+
expect(screen.getByText("95.2%")).toBeTruthy();
94+
// The below-floor rule renders the deliberate null state — the literal words, not a zero.
95+
expect(screen.getAllByText("insufficient data").length).toBeGreaterThanOrEqual(2); // the explainer + the table cell
96+
expect(screen.queryByText("0%")).toBeNull();
97+
// The reproducibility freeze point surfaces the truncated corpus checksum.
98+
expect(screen.getByText(/Reproducibility freeze point/)).toBeTruthy();
99+
expect(screen.getByText(/aaaaaaaaaaaaaaaa/)).toBeTruthy();
100+
// And the walkthrough link points at the docs page.
101+
expect(screen.getByRole("link", { name: /verify this review/i })).toBeTruthy();
102+
});
103+
104+
it("hides the per-rule section entirely when the API response predates rulePrecision (deployment skew) or has no rules (#8231)", async () => {
105+
apiFetch.mockResolvedValue({ ok: true, data: FIXTURE, durationMs: 10 });
106+
renderWithClient(<FairnessReportPage />);
107+
await waitFor(() =>
108+
expect(screen.getByText("Is ORB treating contributors fairly?")).toBeTruthy(),
109+
);
110+
expect(screen.queryByText("Measured accuracy per rule")).toBeNull();
111+
});
112+
54113
it("renders a content-shaped loading skeleton", () => {
55114
apiFetch.mockReturnValue(new Promise(() => {}));
56115
const { container } = renderWithClient(<FairnessReportPage />);

apps/loopover-ui/src/components/site/fairness-report-page.tsx

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { useQuery } from "@tanstack/react-query";
2+
import { Link } from "@tanstack/react-router";
23

34
import { getApiOrigin } from "@/lib/api/origin";
45
import { apiFetch } from "@/lib/api/request";
@@ -251,6 +252,71 @@ export function FairnessReportPage() {
251252
</table>
252253
</TableScroll>
253254
</div>
255+
256+
{data.rulePrecision && data.rulePrecision.rules.length > 0 ? (
257+
<div className="mt-10">
258+
<h2 className="text-token-lg font-medium">Measured accuracy per rule</h2>
259+
<p className="mt-2 text-token-sm text-muted-foreground">
260+
Precision of each automated rule over its human-decided cases in the last{" "}
261+
{data.rulePrecision.windowDays} days. A rule below the decided-sample floor shows{" "}
262+
<span className="font-medium text-foreground">insufficient data</span> — an
263+
unknown is never rendered as 0%. Reproduce these numbers yourself:{" "}
264+
<Link
265+
to="/docs/$slug"
266+
params={{ slug: "verify-this-review" }}
267+
className="underline underline-offset-2"
268+
>
269+
verify this review
270+
</Link>
271+
.
272+
</p>
273+
<TableScroll className="mt-4" label="Measured precision per rule">
274+
<table className="w-full min-w-[28rem] text-left text-token-sm">
275+
<caption className="sr-only">
276+
Decided cases and measured precision per rule.
277+
</caption>
278+
<thead className="text-token-xs text-muted-foreground">
279+
<tr>
280+
<th scope="col" className="pb-2 pr-4 font-medium">
281+
Rule
282+
</th>
283+
<th scope="col" className="pb-2 pr-4 font-medium">
284+
Decided
285+
</th>
286+
<th scope="col" className="pb-2 font-medium">
287+
Precision
288+
</th>
289+
</tr>
290+
</thead>
291+
<tbody>
292+
{data.rulePrecision.rules.map((row) => (
293+
<tr key={row.ruleId} className="border-t border-hairline">
294+
<td className="py-2 pr-4 font-mono text-token-xs">{row.ruleId}</td>
295+
<td className="py-2 pr-4">{intFmt.format(row.decided)}</td>
296+
<td className="py-2">
297+
{row.precision != null ? (
298+
`${pctFmt.format(row.precision * 100)}%`
299+
) : (
300+
<span className="text-muted-foreground">insufficient data</span>
301+
)}
302+
</td>
303+
</tr>
304+
))}
305+
</tbody>
306+
</table>
307+
</TableScroll>
308+
{data.rulePrecision.latestBacktestRun ? (
309+
<p className="mt-3 text-token-xs text-muted-foreground">
310+
Reproducibility freeze point: corpus checksum{" "}
311+
<span className="font-mono">
312+
{data.rulePrecision.latestBacktestRun.corpusChecksum.slice(0, 16)}
313+
</span>{" "}
314+
from the latest persisted backtest run (
315+
{new Date(data.rulePrecision.latestBacktestRun.at).toLocaleDateString()}).
316+
</p>
317+
) : null}
318+
</div>
319+
) : null}
254320
</div>
255321
) : null}
256322
</StateBoundary>

apps/loopover-ui/src/components/site/proof-of-power-stats-model.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,21 @@ export type PublicStats = {
5858
merged: number;
5959
filteredPct: number | null;
6060
}>;
61+
62+
/** Measured per-rule precision + the reproducibility freeze point (#8230/#8231). Optional-chained by
63+
* consumers: until the backend carrying it is deployed, an older /v1/public/stats response simply won't
64+
* have the field yet, and every surface must degrade to hiding the section rather than throw. */
65+
rulePrecision?: {
66+
windowDays: number;
67+
rules: Array<{
68+
ruleId: string;
69+
decided: number;
70+
/** confirmed / decided; null below the decided-sample floor -- rendered as "insufficient data", NEVER 0%. */
71+
precision: number | null;
72+
}>;
73+
reversals: { reopened: number; reverted: number; superseded: number };
74+
latestBacktestRun: { corpusChecksum: string; at: string } | null;
75+
};
6176
};
6277

6378
/** Relative "updated Ns ago" label from the payload's updatedAt (mirrors MetaStrip's freshness logic). */

apps/loopover-ui/src/routes/docs.index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ const AUDIENCES: Audience[] = [
9595
{ to: "/docs/upstream-drift", label: "Upstream drift" },
9696
{ to: "/docs/ai-summaries", label: "AI summaries policy" },
9797
{ to: "/docs/backtest-calibration", label: "Backtest & calibration" },
98+
{ to: "/docs/verify-this-review", label: "Verify this review" },
9899
],
99100
},
100101
{

0 commit comments

Comments
 (0)