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
1 change: 1 addition & 0 deletions project-access-denial-appeal-guard/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
reports/frames/
40 changes: 40 additions & 0 deletions project-access-denial-appeal-guard/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Project Access Denial and Appeal Guard

Self-contained synthetic module for SCIBASE issue #11, User & Project Management.

The guard focuses on a narrow decision point: denied restricted-project access requests and appeal packets should be fair, timely, independently reviewed, requester-safe, and auditable before the final user-facing decision is sent.

## What It Checks

- Stable denied access request ids and requester ids.
- Specific denial reason codes and rationales.
- Requester-safe redaction of private denial and appeal notes.
- Appeal submission inside the configured appeal window.
- Independent appeal reviewer identity and eligible governance role.
- Required training, data-use agreement, and IRB evidence by project classification.
- Least-privilege object scope for restricted projects.
- User-facing audit receipts with reason code, appeal deadline, evidence checklist, decision timestamp, and appeal outcome.

This is distinct from broad RBAC ledgers, privacy access review campaigns, member offboarding, break-glass access, collaborator conflict checks, data-residency transfers, and object-permission drift. It is specifically about the post-denial appeal workflow and the receipt that a denied researcher receives.

## Run

```sh
npm run check
npm test
npm run demo
npm run verify-video
```

## Outputs

`npm run demo` writes:

- `reports/clean-audit.json`
- `reports/risky-audit.json`
- `reports/risky-review.md`
- `reports/summary.svg`
- `reports/manifest.json`
- `reports/demo.mp4`

The sample data is synthetic only. The module does not call identity providers, SAML/OAuth/ORCID services, GitHub, payment processors, private user/project systems, or external APIs.
46 changes: 46 additions & 0 deletions project-access-denial-appeal-guard/demo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
"use strict";

const fs = require("node:fs");
const path = require("node:path");
const {
evaluateAccessDenialAppeals,
renderMarkdownReport,
renderSvgSummary
} = require("./index");
const { cleanPacket, riskyPacket } = require("./sample-data");

const reportsDir = path.join(__dirname, "reports");
fs.mkdirSync(reportsDir, { recursive: true });

const clean = evaluateAccessDenialAppeals(cleanPacket);
const risky = evaluateAccessDenialAppeals(riskyPacket);

fs.writeFileSync(path.join(reportsDir, "clean-audit.json"), `${JSON.stringify(clean, null, 2)}\n`);
fs.writeFileSync(path.join(reportsDir, "risky-audit.json"), `${JSON.stringify(risky, null, 2)}\n`);
fs.writeFileSync(path.join(reportsDir, "risky-review.md"), renderMarkdownReport(risky, riskyPacket));
fs.writeFileSync(path.join(reportsDir, "summary.svg"), renderSvgSummary(risky));
fs.writeFileSync(
path.join(reportsDir, "manifest.json"),
`${JSON.stringify(
{
generatedAt: new Date().toISOString(),
module: "project-access-denial-appeal-guard",
cleanStatus: clean.status,
riskyStatus: risky.status,
riskyFindings: risky.findings.length,
artifacts: [
"clean-audit.json",
"risky-audit.json",
"risky-review.md",
"summary.svg",
"demo.mp4"
]
},
null,
2
)}\n`
);

console.log(`Clean packet: ${clean.status} (${clean.findings.length} findings)`);
console.log(`Risky packet: ${risky.status} (${risky.findings.length} findings)`);
console.log(`Wrote reports to ${reportsDir}`);
Loading