Skip to content
10 changes: 10 additions & 0 deletions reputation-appeal-evidence-guard/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"name": "scibase-reputation-appeal-evidence-guard",
"version": "1.0.0",
"type": "module",
"private": true,
"scripts": {
"test": "node --test test/*.test.js",
"demo": "node scripts/demo.js"
}
}
29 changes: 29 additions & 0 deletions reputation-appeal-evidence-guard/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Reputation Appeal Evidence Guard

This module contributes to SCIBASE issue #15, the Community & User Reputation System.

It evaluates appeals against reputation penalties or reinstatement requests before they can alter public trust signals. The guard checks that each appeal has enough evidence, independent reviewers, an on-time decision path, a clear requested outcome, and bounded probation when reputation is restored.

## What It Produces

- A deterministic appeal evaluation packet.
- Hold/release decisions for each appeal.
- Reviewer checklist text for the pull request.
- Demo report artifacts under `reports/`.

## Local Verification

```bash
npm test
npm run demo
```

## Demo Evidence

The demo transcript is captured in `reports/demo-transcript.md`. The demo generates these reviewer artifacts:

- `reports/appeal-evidence-report.md`
- `reports/appeal-evidence-packet.json`
- `reports/summary.svg`

The demo data is synthetic and does not include private user records, institutional credentials, or external services.
108 changes: 108 additions & 0 deletions reputation-appeal-evidence-guard/reports/appeal-evidence-packet.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
{
"title": "SCIBASE Reputation Appeal Evidence Guard",
"issue": "SCIBASE.AI#15",
"claim": "/claim #15",
"evaluation": {
"status": "hold_reputation_change",
"generatedAt": "2026-06-13T18:00:00.000Z",
"packetId": "scibase-reputation-appeal-demo",
"digest": "63127cd902d761c84395f0c2c5bf2491a6b6011195e74a7259d94411fb0ceb2c",
"counts": {
"users": 3,
"sanctions": 2,
"appeals": 2,
"blockers": 5,
"warnings": 1,
"heldAppeals": 1
},
"blockers": [
{
"code": "insufficient_evidence",
"appealId": "app-review-abuse",
"message": "Appeal does not include enough evidence for peer validation.",
"evidenceItems": 1,
"requiredEvidenceItems": 2
},
{
"code": "insufficient_independent_review",
"appealId": "app-review-abuse",
"message": "Appeal does not have enough independent conflict-free reviewers.",
"independentReviewers": 1,
"requiredIndependentReviewers": 2
},
{
"code": "same_actor_reappeal",
"appealId": "app-review-abuse",
"message": "Original decision actor cannot also decide the appeal."
},
{
"code": "appeal_window_expired",
"appealId": "app-review-abuse",
"message": "Appeal was submitted outside the configured appeal window.",
"appealAgeDays": 104,
"maxAppealAgeDays": 45
},
{
"code": "probation_too_long",
"appealId": "app-review-abuse",
"message": "Reinstatement probation exceeds the configured maximum.",
"probationDays": 180,
"maxProbationDays": 90
}
],
"warnings": [
{
"code": "appeal_sla_too_long",
"appealId": "app-review-abuse",
"message": "Decision due date exceeds the configured appeal SLA.",
"slaHours": 2664,
"maxSlaHours": 168
}
],
"decisions": [
{
"appealId": "app-duplicate-credit",
"userId": "usr-dataset-curator",
"sanctionId": "san-duplicate-credit",
"decision": "release",
"reasons": [],
"requiredActions": []
},
{
"appealId": "app-review-abuse",
"userId": "usr-protocol-author",
"sanctionId": "san-review-abuse",
"decision": "hold",
"reasons": [
"insufficient_evidence",
"insufficient_independent_review",
"same_actor_reappeal",
"appeal_window_expired",
"probation_too_long"
],
"requiredActions": [
"Attach at least 2 evidence items.",
"Add 2 independent reviewer decisions with no conflicts.",
"Assign a separate appeal decision actor.",
"Escalate expired appeals to a governance exception review.",
"Reduce reinstatement probation to the policy maximum."
]
}
],
"policy": {
"maxAppealAgeDays": 45,
"slaHours": 168,
"minIndependentReviewers": 2,
"minEvidenceItems": 2,
"maxProbationDays": 90,
"requireActorSeparation": true
}
},
"reviewerChecklist": [
"Appeals reference an existing reputation profile and sanction.",
"Each appeal has the configured evidence threshold.",
"Independent reviewers disclose no conflicts.",
"Original penalty actors do not decide their own appeals.",
"Reinstatement probation stays bounded by policy."
]
}
24 changes: 24 additions & 0 deletions reputation-appeal-evidence-guard/reports/appeal-evidence-report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Reputation Appeal Evidence Guard Report

Issue: SCIBASE.AI#15
Claim marker: `/claim #15`
Status: `hold_reputation_change`
Digest: `63127cd902d761c84395f0c2c5bf2491a6b6011195e74a7259d94411fb0ceb2c`

## Reviewer Checklist
- Appeals reference an existing reputation profile and sanction.
- Each appeal has the configured evidence threshold.
- Independent reviewers disclose no conflicts.
- Original penalty actors do not decide their own appeals.
- Reinstatement probation stays bounded by policy.

## Blockers
- insufficient_evidence: Appeal does not include enough evidence for peer validation.
- insufficient_independent_review: Appeal does not have enough independent conflict-free reviewers.
- same_actor_reappeal: Original decision actor cannot also decide the appeal.
- appeal_window_expired: Appeal was submitted outside the configured appeal window.
- probation_too_long: Reinstatement probation exceeds the configured maximum.

## Appeal Decisions
- app-duplicate-credit: release
- app-review-abuse: hold (insufficient_evidence, insufficient_independent_review, same_actor_reappeal, appeal_window_expired, probation_too_long)
35 changes: 35 additions & 0 deletions reputation-appeal-evidence-guard/reports/demo-transcript.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Reputation Appeal Evidence Guard Demo Transcript

Date verified: 2026-06-15

Commands run from `reputation-appeal-evidence-guard`:

```bash
npm test
npm run demo
```

Test result:

```text
5 tests passed
0 tests failed
```

Demo output:

```json
{
"status": "hold_reputation_change",
"digest": "63127cd902d761c84395f0c2c5bf2491a6b6011195e74a7259d94411fb0ceb2c",
"blockers": 5,
"heldAppeals": 1,
"reportsDir": "reputation-appeal-evidence-guard/reports"
}
```

Generated reviewer artifacts:

- `reports/appeal-evidence-report.md`
- `reports/appeal-evidence-packet.json`
- `reports/summary.svg`
1 change: 1 addition & 0 deletions reputation-appeal-evidence-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.
29 changes: 29 additions & 0 deletions reputation-appeal-evidence-guard/scripts/demo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";

import {
buildReviewerPacket,
demoPacket,
renderMarkdownReport,
renderSvgSummary
} from "../src/index.js";

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const moduleRoot = path.resolve(__dirname, "..");
const reportsDir = path.join(moduleRoot, "reports");
const packet = demoPacket();
const reviewerPacket = buildReviewerPacket(packet, { now: packet.generatedAt });

fs.mkdirSync(reportsDir, { recursive: true });
fs.writeFileSync(path.join(reportsDir, "appeal-evidence-packet.json"), `${JSON.stringify(reviewerPacket, null, 2)}\n`);
fs.writeFileSync(path.join(reportsDir, "appeal-evidence-report.md"), renderMarkdownReport(packet, { now: packet.generatedAt }));
fs.writeFileSync(path.join(reportsDir, "summary.svg"), renderSvgSummary(packet, { now: packet.generatedAt }));

console.log(JSON.stringify({
status: reviewerPacket.evaluation.status,
digest: reviewerPacket.evaluation.digest,
blockers: reviewerPacket.evaluation.counts.blockers,
heldAppeals: reviewerPacket.evaluation.counts.heldAppeals,
reportsDir
}, null, 2));
Loading