Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions reputation-review-abuse-guard/demo.js
Original file line number Diff line number Diff line change
@@ -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 = `<svg xmlns="http://www.w3.org/2000/svg" width="960" height="540" viewBox="0 0 960 540">
<rect width="960" height="540" fill="#f9fafb"/>
<text x="48" y="64" font-family="Arial" font-size="28" font-weight="700" fill="#172033">Review-Abuse Reputation Guard</text>
<text x="48" y="102" font-family="Arial" font-size="16" fill="#4f5d75">Peer review credit moderation before leaderboard updates</text>
${report.reviews.map((review, index) => {
const y = 150 + index * 82;
const color = review.decision === "count-reputation-credit" ? "#047857" : "#b45309";
return `<rect x="48" y="${y - 34}" width="864" height="58" rx="6" fill="#ffffff" stroke="#d7dce6"/>
<text x="72" y="${y - 8}" font-family="Arial" font-size="18" font-weight="700" fill="#172033">${review.id}</text>
<text x="72" y="${y + 16}" font-family="Arial" font-size="14" fill="${color}">${review.decision}</text>
<text x="310" y="${y + 16}" font-family="Arial" font-size="14" fill="#4f5d75">${review.findings.length} finding(s), delta ${review.scoreDelta}</text>`;
}).join("\n ")}
</svg>
`;

fs.writeFileSync(path.join(reportsDir, "summary.svg"), svg);
console.log(JSON.stringify(report.summary, null, 2));
68 changes: 68 additions & 0 deletions reputation-review-abuse-guard/index.js
Original file line number Diff line number Diff line change
@@ -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 };
33 changes: 33 additions & 0 deletions reputation-review-abuse-guard/readme.md
Original file line number Diff line number Diff line change
@@ -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.
45 changes: 45 additions & 0 deletions reputation-review-abuse-guard/render-video.js
Original file line number Diff line number Diff line change
@@ -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);
Binary file added reputation-review-abuse-guard/reports/demo.mp4
Binary file not shown.
71 changes: 71 additions & 0 deletions reputation-review-abuse-guard/reports/review-abuse-report.json
Original file line number Diff line number Diff line change
@@ -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"
]
}
]
}
41 changes: 41 additions & 0 deletions reputation-review-abuse-guard/reports/review-abuse-report.md
Original file line number Diff line number Diff line change
@@ -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
21 changes: 21 additions & 0 deletions reputation-review-abuse-guard/reports/summary.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading