diff --git a/reputation-review-abuse-guard/demo.js b/reputation-review-abuse-guard/demo.js
new file mode 100644
index 00000000..b810e3b3
--- /dev/null
+++ b/reputation-review-abuse-guard/demo.js
@@ -0,0 +1,56 @@
+const fs = require("fs");
+const path = require("path");
+const { reputationBatch } = require("./sample-data");
+const { evaluateBatch } = require("./index");
+
+const reportsDir = path.join(__dirname, "reports");
+fs.mkdirSync(reportsDir, { recursive: true });
+
+const report = evaluateBatch(reputationBatch);
+fs.writeFileSync(path.join(reportsDir, "review-abuse-report.json"), `${JSON.stringify(report, null, 2)}\n`);
+
+const markdown = [
+ "# Reputation Review-Abuse Guard Report",
+ "",
+ `Batch: ${report.batchId}`,
+ `Generated: ${report.generatedAt}`,
+ "",
+ "## Summary",
+ "",
+ `- Reviews evaluated: ${report.summary.total}`,
+ `- Credits counted: ${report.summary["count-reputation-credit"] || 0}`,
+ `- Credits held: ${report.summary["hold-reputation-credit"] || 0}`,
+ `- Findings: ${report.summary.findingCount}`,
+ "",
+ "## Decisions",
+ "",
+ ...report.reviews.flatMap((review) => [
+ `### ${review.id}`,
+ "",
+ `- Reviewer: ${review.reviewer}`,
+ `- Decision: ${review.decision}`,
+ `- Findings: ${review.findings.length ? review.findings.join("; ") : "none"}`,
+ `- Actions: ${review.actions.join("; ")}`,
+ ""
+ ])
+].join("\n").trim();
+
+fs.writeFileSync(path.join(reportsDir, "review-abuse-report.md"), `${markdown}\n`);
+
+const svg = `
+`;
+
+fs.writeFileSync(path.join(reportsDir, "summary.svg"), svg);
+console.log(JSON.stringify(report.summary, null, 2));
diff --git a/reputation-review-abuse-guard/index.js b/reputation-review-abuse-guard/index.js
new file mode 100644
index 00000000..7b155ef5
--- /dev/null
+++ b/reputation-review-abuse-guard/index.js
@@ -0,0 +1,68 @@
+const MIN_REVIEW_WORDS = 40;
+const HIGH_REPUTATION_DELTA = 10;
+
+function evaluateReview(review) {
+ const findings = [];
+
+ if (review.reviewerAffiliation === review.authorAffiliation) {
+ findings.push("reviewer shares affiliation with target author");
+ }
+ if (review.commentWords < MIN_REVIEW_WORDS && review.scoreDelta >= HIGH_REPUTATION_DELTA) {
+ findings.push("high reputation delta from low-effort review text");
+ }
+ if (review.reciprocalReviewIds.length > 0) {
+ findings.push(`reciprocal review link detected: ${review.reciprocalReviewIds.join(", ")}`);
+ }
+ if (review.anonymousMode && review.leakedIdentityText) {
+ findings.push("anonymous review contains identity leakage");
+ }
+ if (review.endorsementCluster.includes(review.reviewer) && review.endorsementCluster.includes(review.targetAuthor)) {
+ findings.push("reviewer and target author appear in same endorsement cluster");
+ }
+
+ let decision = "count-reputation-credit";
+ if (findings.some((finding) => finding.includes("identity leakage"))) {
+ decision = "redact-before-credit";
+ }
+ if (findings.length >= 2) {
+ decision = "hold-reputation-credit";
+ }
+
+ const actions = {
+ "count-reputation-credit": ["apply reputation credit", "record review audit"],
+ "redact-before-credit": ["redact identity leakage", "queue moderator check before credit"],
+ "hold-reputation-credit": ["hold reputation credit", "route to trust-and-safety review", "suppress leaderboard update"]
+ }[decision];
+
+ return {
+ id: review.id,
+ reviewer: review.reviewer,
+ targetProject: review.targetProject,
+ decision,
+ scoreDelta: review.scoreDelta,
+ findings,
+ actions
+ };
+}
+
+function evaluateBatch(batch) {
+ const reviews = batch.reviews.map(evaluateReview);
+ const summary = reviews.reduce(
+ (acc, review) => {
+ acc.total += 1;
+ acc[review.decision] = (acc[review.decision] || 0) + 1;
+ acc.findingCount += review.findings.length;
+ return acc;
+ },
+ { total: 0, findingCount: 0 }
+ );
+
+ return {
+ batchId: batch.id,
+ generatedAt: batch.generatedAt,
+ summary,
+ reviews
+ };
+}
+
+module.exports = { MIN_REVIEW_WORDS, HIGH_REPUTATION_DELTA, evaluateReview, evaluateBatch };
diff --git a/reputation-review-abuse-guard/readme.md b/reputation-review-abuse-guard/readme.md
new file mode 100644
index 00000000..f1331fce
--- /dev/null
+++ b/reputation-review-abuse-guard/readme.md
@@ -0,0 +1,33 @@
+# Reputation Review-Abuse Guard
+
+This module adds a focused moderation layer for the Community & User Reputation System. It decides whether peer-review activity should count toward reputation and leaderboard updates.
+
+It is intentionally separate from broad reputation scoring, contributor credit graphs, review templates, profile dashboards, badge systems, and leaderboards. This guard only handles abuse and conflict signals around review-derived reputation credit.
+
+## What It Checks
+
+- Shared-affiliation conflicts between reviewer and target author
+- Reciprocal review links
+- High reputation deltas from low-effort review text
+- Anonymous-review identity leakage
+- Endorsement-cluster farming signals
+- Deterministic count, hold, and moderation actions
+
+## Demo
+
+Run:
+
+```bash
+node reputation-review-abuse-guard/test.js
+node reputation-review-abuse-guard/demo.js
+node reputation-review-abuse-guard/render-video.js
+```
+
+Generated artifacts:
+
+- `reports/review-abuse-report.json`
+- `reports/review-abuse-report.md`
+- `reports/summary.svg`
+- `reports/demo.mp4`
+
+All data is synthetic. The module does not call identity providers, private user profiles, live reputation ledgers, payment systems, credentials, or SCIBASE production services.
diff --git a/reputation-review-abuse-guard/render-video.js b/reputation-review-abuse-guard/render-video.js
new file mode 100644
index 00000000..4f3acc47
--- /dev/null
+++ b/reputation-review-abuse-guard/render-video.js
@@ -0,0 +1,45 @@
+const fs = require("fs");
+const path = require("path");
+const { spawnSync } = require("child_process");
+
+const reportsDir = path.join(__dirname, "reports");
+fs.mkdirSync(reportsDir, { recursive: true });
+
+const width = 960;
+const height = 540;
+const ppm = path.join(reportsDir, "demo-frame.ppm");
+const mp4 = path.join(reportsDir, "demo.mp4");
+
+function pixel(x, y) {
+ if (y < 112) return [23, 32, 51];
+ if (x > 46 && x < 914 && y > 126 && y < 188) return [255, 255, 255];
+ if (x > 46 && x < 914 && y > 210 && y < 272) return [255, 255, 255];
+ if (x > 46 && x < 914 && y > 294 && y < 356) return [255, 255, 255];
+ if (x > 46 && x < 914 && y > 378 && y < 440) return [255, 255, 255];
+ if (x > 64 && x < 238 && y > 140 && y < 174) return [4, 120, 87];
+ if (x > 64 && x < 238 && y > 224 && y < 258) return [180, 83, 9];
+ if (x > 64 && x < 238 && y > 308 && y < 342) return [180, 83, 9];
+ if (x > 64 && x < 238 && y > 392 && y < 426) return [4, 120, 87];
+ return [249, 250, 251];
+}
+
+const header = `P6\n${width} ${height}\n255\n`;
+const body = Buffer.alloc(width * height * 3);
+for (let y = 0; y < height; y += 1) {
+ for (let x = 0; x < width; x += 1) {
+ const offset = (y * width + x) * 3;
+ const [r, g, b] = pixel(x, y);
+ body[offset] = r;
+ body[offset + 1] = g;
+ body[offset + 2] = b;
+ }
+}
+fs.writeFileSync(ppm, Buffer.concat([Buffer.from(header), body]));
+
+const result = spawnSync("ffmpeg", [
+ "-y", "-loop", "1", "-framerate", "24", "-i", ppm,
+ "-t", "5", "-vf", "format=yuv420p", "-movflags", "+faststart", mp4
+], { stdio: "inherit" });
+
+if (result.status !== 0) throw new Error("ffmpeg failed to render demo video");
+console.log(mp4);
diff --git a/reputation-review-abuse-guard/reports/demo.mp4 b/reputation-review-abuse-guard/reports/demo.mp4
new file mode 100644
index 00000000..d54cf18c
Binary files /dev/null and b/reputation-review-abuse-guard/reports/demo.mp4 differ
diff --git a/reputation-review-abuse-guard/reports/review-abuse-report.json b/reputation-review-abuse-guard/reports/review-abuse-report.json
new file mode 100644
index 00000000..a8c815a5
--- /dev/null
+++ b/reputation-review-abuse-guard/reports/review-abuse-report.json
@@ -0,0 +1,71 @@
+{
+ "batchId": "reputation-batch-2026-05-29",
+ "generatedAt": "2026-05-29T13:02:00.000Z",
+ "summary": {
+ "total": 4,
+ "findingCount": 7,
+ "count-reputation-credit": 2,
+ "hold-reputation-credit": 2
+ },
+ "reviews": [
+ {
+ "id": "review-101",
+ "reviewer": "ana",
+ "targetProject": "proj-climate-7",
+ "decision": "count-reputation-credit",
+ "scoreDelta": 8,
+ "findings": [],
+ "actions": [
+ "apply reputation credit",
+ "record review audit"
+ ]
+ },
+ {
+ "id": "review-102",
+ "reviewer": "chen",
+ "targetProject": "proj-neuro-4",
+ "decision": "hold-reputation-credit",
+ "scoreDelta": 15,
+ "findings": [
+ "reviewer shares affiliation with target author",
+ "high reputation delta from low-effort review text",
+ "reciprocal review link detected: review-099",
+ "reviewer and target author appear in same endorsement cluster"
+ ],
+ "actions": [
+ "hold reputation credit",
+ "route to trust-and-safety review",
+ "suppress leaderboard update"
+ ]
+ },
+ {
+ "id": "review-103",
+ "reviewer": "eli",
+ "targetProject": "proj-math-2",
+ "decision": "hold-reputation-credit",
+ "scoreDelta": 11,
+ "findings": [
+ "high reputation delta from low-effort review text",
+ "anonymous review contains identity leakage",
+ "reviewer and target author appear in same endorsement cluster"
+ ],
+ "actions": [
+ "hold reputation credit",
+ "route to trust-and-safety review",
+ "suppress leaderboard update"
+ ]
+ },
+ {
+ "id": "review-104",
+ "reviewer": "hugo",
+ "targetProject": "proj-protein-8",
+ "decision": "count-reputation-credit",
+ "scoreDelta": 5,
+ "findings": [],
+ "actions": [
+ "apply reputation credit",
+ "record review audit"
+ ]
+ }
+ ]
+}
diff --git a/reputation-review-abuse-guard/reports/review-abuse-report.md b/reputation-review-abuse-guard/reports/review-abuse-report.md
new file mode 100644
index 00000000..8ec94c39
--- /dev/null
+++ b/reputation-review-abuse-guard/reports/review-abuse-report.md
@@ -0,0 +1,41 @@
+# Reputation Review-Abuse Guard Report
+
+Batch: reputation-batch-2026-05-29
+Generated: 2026-05-29T13:02:00.000Z
+
+## Summary
+
+- Reviews evaluated: 4
+- Credits counted: 2
+- Credits held: 2
+- Findings: 7
+
+## Decisions
+
+### review-101
+
+- Reviewer: ana
+- Decision: count-reputation-credit
+- Findings: none
+- Actions: apply reputation credit; record review audit
+
+### review-102
+
+- Reviewer: chen
+- Decision: hold-reputation-credit
+- Findings: reviewer shares affiliation with target author; high reputation delta from low-effort review text; reciprocal review link detected: review-099; reviewer and target author appear in same endorsement cluster
+- Actions: hold reputation credit; route to trust-and-safety review; suppress leaderboard update
+
+### review-103
+
+- Reviewer: eli
+- Decision: hold-reputation-credit
+- Findings: high reputation delta from low-effort review text; anonymous review contains identity leakage; reviewer and target author appear in same endorsement cluster
+- Actions: hold reputation credit; route to trust-and-safety review; suppress leaderboard update
+
+### review-104
+
+- Reviewer: hugo
+- Decision: count-reputation-credit
+- Findings: none
+- Actions: apply reputation credit; record review audit
diff --git a/reputation-review-abuse-guard/reports/summary.svg b/reputation-review-abuse-guard/reports/summary.svg
new file mode 100644
index 00000000..7ddecffa
--- /dev/null
+++ b/reputation-review-abuse-guard/reports/summary.svg
@@ -0,0 +1,21 @@
+
diff --git a/reputation-review-abuse-guard/sample-data.js b/reputation-review-abuse-guard/sample-data.js
new file mode 100644
index 00000000..40523288
--- /dev/null
+++ b/reputation-review-abuse-guard/sample-data.js
@@ -0,0 +1,61 @@
+const reputationBatch = {
+ id: "reputation-batch-2026-05-29",
+ generatedAt: "2026-05-29T13:02:00.000Z",
+ reviews: [
+ {
+ id: "review-101",
+ reviewer: "ana",
+ targetProject: "proj-climate-7",
+ targetAuthor: "bao",
+ reviewerAffiliation: "North Lab",
+ authorAffiliation: "South Lab",
+ scoreDelta: 8,
+ commentWords: 142,
+ reciprocalReviewIds: [],
+ anonymousMode: false,
+ endorsementCluster: ["li", "mina"]
+ },
+ {
+ id: "review-102",
+ reviewer: "chen",
+ targetProject: "proj-neuro-4",
+ targetAuthor: "dina",
+ reviewerAffiliation: "Vector Institute",
+ authorAffiliation: "Vector Institute",
+ scoreDelta: 15,
+ commentWords: 18,
+ reciprocalReviewIds: ["review-099"],
+ anonymousMode: false,
+ endorsementCluster: ["dina", "eli", "chen"]
+ },
+ {
+ id: "review-103",
+ reviewer: "eli",
+ targetProject: "proj-math-2",
+ targetAuthor: "faye",
+ reviewerAffiliation: "Open Methods Group",
+ authorAffiliation: "Cedar University",
+ scoreDelta: 11,
+ commentWords: 9,
+ reciprocalReviewIds: [],
+ anonymousMode: true,
+ leakedIdentityText: "As your labmate Eli mentioned yesterday",
+ endorsementCluster: ["faye", "gita", "eli"]
+ },
+ {
+ id: "review-104",
+ reviewer: "hugo",
+ targetProject: "proj-protein-8",
+ targetAuthor: "iris",
+ reviewerAffiliation: "North Lab",
+ authorAffiliation: "West Lab",
+ scoreDelta: 5,
+ commentWords: 64,
+ reciprocalReviewIds: [],
+ anonymousMode: false,
+ endorsementCluster: ["jules"]
+ }
+ ]
+};
+
+module.exports = { reputationBatch };
diff --git a/reputation-review-abuse-guard/test.js b/reputation-review-abuse-guard/test.js
new file mode 100644
index 00000000..559c7997
--- /dev/null
+++ b/reputation-review-abuse-guard/test.js
@@ -0,0 +1,20 @@
+const assert = require("assert");
+const { reputationBatch } = require("./sample-data");
+const { evaluateBatch } = require("./index");
+
+const report = evaluateBatch(reputationBatch);
+
+assert.strictEqual(report.summary.total, 4);
+assert.strictEqual(report.summary["count-reputation-credit"], 2);
+assert.strictEqual(report.summary["hold-reputation-credit"], 2);
+
+const conflict = report.reviews.find((review) => review.id === "review-102");
+assert(conflict.findings.some((finding) => finding.includes("shares affiliation")));
+assert(conflict.findings.some((finding) => finding.includes("reciprocal")));
+assert(conflict.actions.includes("suppress leaderboard update"));
+
+const anonymous = report.reviews.find((review) => review.id === "review-103");
+assert(anonymous.findings.some((finding) => finding.includes("identity leakage")));
+assert.strictEqual(anonymous.decision, "hold-reputation-credit");
+
+console.log("reputation-review-abuse-guard tests passed");