From d79e6b5f7d2fb89523126550b699aaa6050137a9 Mon Sep 17 00:00:00 2001
From: taherd <183945978+taherdhanera@users.noreply.github.com>
Date: Fri, 22 May 2026 23:17:38 +0530
Subject: [PATCH] Add project visibility transition guard
---
project-visibility-transition-guard/README.md | 37 ++
.../demo-video.js | 173 ++++++++++
project-visibility-transition-guard/demo.js | 18 +
project-visibility-transition-guard/index.js | 292 ++++++++++++++++
.../package.json | 14 +
.../reports/demo.webm | Bin 0 -> 12099 bytes
.../reports/reviewer-packet.md | 73 ++++
.../reports/summary.json | 326 ++++++++++++++++++
.../reports/summary.svg | 16 +
.../requirements-map.md | 18 +
.../sample-data.js | 76 ++++
project-visibility-transition-guard/test.js | 94 +++++
12 files changed, 1137 insertions(+)
create mode 100644 project-visibility-transition-guard/README.md
create mode 100644 project-visibility-transition-guard/demo-video.js
create mode 100644 project-visibility-transition-guard/demo.js
create mode 100644 project-visibility-transition-guard/index.js
create mode 100644 project-visibility-transition-guard/package.json
create mode 100644 project-visibility-transition-guard/reports/demo.webm
create mode 100644 project-visibility-transition-guard/reports/reviewer-packet.md
create mode 100644 project-visibility-transition-guard/reports/summary.json
create mode 100644 project-visibility-transition-guard/reports/summary.svg
create mode 100644 project-visibility-transition-guard/requirements-map.md
create mode 100644 project-visibility-transition-guard/sample-data.js
create mode 100644 project-visibility-transition-guard/test.js
diff --git a/project-visibility-transition-guard/README.md b/project-visibility-transition-guard/README.md
new file mode 100644
index 00000000..2af65009
--- /dev/null
+++ b/project-visibility-transition-guard/README.md
@@ -0,0 +1,37 @@
+# Project Visibility Transition Guard
+
+Self-contained User & Project Management slice for
+`SCIBASE-AI/SCIBASE.AI#11`.
+
+The guard evaluates private, institutional-only, or invitation-only scientific
+workspaces before they are made public. It checks required approvals,
+collaborator profile consent, object-level document/code/data permissions,
+sensitive labels, public readiness, active IRB/funder holds, external partner
+access, and immutable audit evidence.
+
+This is intentionally separate from workspace/RBAC ledgers, privacy access
+reviews, identity recovery, member lifecycle/offboarding, institutional
+recertification, anonymous-review escrow, identity merge/export, data-room
+consent, researcher profile sync, archive handoff, access-audit anomaly, role
+delegation, invitation-domain/MFA, funding attribution, service-token governance,
+deletion/erasure, break-glass access, and contribution-credit gates. Its job is
+to stop unsafe public visibility transitions.
+
+## Run
+
+```bash
+npm run check
+npm test
+npm run demo
+npm run demo:video
+```
+
+## Outputs
+
+- `reports/summary.json`
+- `reports/reviewer-packet.md`
+- `reports/summary.svg`
+- `reports/demo.webm`
+
+All data is synthetic. The module does not call OAuth, SAML, ORCID, storage,
+profile, permission, email, audit-log, or external services.
diff --git a/project-visibility-transition-guard/demo-video.js b/project-visibility-transition-guard/demo-video.js
new file mode 100644
index 00000000..30d6159a
--- /dev/null
+++ b/project-visibility-transition-guard/demo-video.js
@@ -0,0 +1,173 @@
+const fs = require("fs");
+const os = require("os");
+const path = require("path");
+const { execFileSync } = require("child_process");
+
+const reportDir = path.join(__dirname, "reports");
+const outputPath = path.join(reportDir, "demo.webm");
+
+const chromeCandidates = [
+ process.env.CHROME_PATH,
+ "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
+ "C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe",
+ "C:\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe",
+ "C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe"
+].filter(Boolean);
+
+function findBrowser() {
+ const found = chromeCandidates.find((candidate) => fs.existsSync(candidate));
+ if (!found) {
+ throw new Error("Chrome or Edge was not found. Set CHROME_PATH to generate reports/demo.webm.");
+ }
+ return found;
+}
+
+function fileUrl(filePath) {
+ return `file:///${filePath.replace(/\\/g, "/")}`;
+}
+
+const html = String.raw`
+
+
+
+ Project visibility transition guard demo
+
+
+
+
+ recording
+
+
+`;
+
+fs.mkdirSync(reportDir, { recursive: true });
+
+const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "visibility-transition-demo-"));
+const htmlPath = path.join(tempDir, "demo.html");
+const profileDir = path.join(tempDir, "profile");
+fs.writeFileSync(htmlPath, html, "utf8");
+
+const stdout = execFileSync(
+ findBrowser(),
+ [
+ "--headless=new",
+ "--disable-gpu",
+ "--disable-dev-shm-usage",
+ "--autoplay-policy=no-user-gesture-required",
+ "--run-all-compositor-stages-before-draw",
+ "--virtual-time-budget=7500",
+ `--user-data-dir=${profileDir}`,
+ "--dump-dom",
+ fileUrl(htmlPath)
+ ],
+ { encoding: "utf8", maxBuffer: 30 * 1024 * 1024 }
+);
+
+const match = stdout.match(/data:video\/webm;base64,([A-Za-z0-9+/=]+)/);
+if (!match) {
+ throw new Error(`Demo video generation failed. Browser output ended with: ${stdout.slice(-600)}`);
+}
+
+fs.writeFileSync(outputPath, Buffer.from(match[1], "base64"));
+console.log(`Generated ${path.relative(process.cwd(), outputPath)}`);
diff --git a/project-visibility-transition-guard/demo.js b/project-visibility-transition-guard/demo.js
new file mode 100644
index 00000000..97c3730c
--- /dev/null
+++ b/project-visibility-transition-guard/demo.js
@@ -0,0 +1,18 @@
+const fs = require("fs");
+const path = require("path");
+const { project } = require("./sample-data");
+const { buildReviewPacket, renderMarkdownReport, renderSvgSummary } = require("./index");
+
+const reportDir = path.join(__dirname, "reports");
+fs.mkdirSync(reportDir, { recursive: true });
+
+const packet = buildReviewPacket(project);
+
+fs.writeFileSync(path.join(reportDir, "summary.json"), `${JSON.stringify(packet, null, 2)}\n`, "utf8");
+fs.writeFileSync(path.join(reportDir, "reviewer-packet.md"), renderMarkdownReport(packet), "utf8");
+fs.writeFileSync(path.join(reportDir, "summary.svg"), renderSvgSummary(packet), "utf8");
+
+console.log(`Generated reports for ${packet.guard}`);
+console.log(`Decision: ${packet.decision}`);
+console.log(`Score: ${packet.score}`);
+console.log(`Findings: ${packet.findings.length}`);
diff --git a/project-visibility-transition-guard/index.js b/project-visibility-transition-guard/index.js
new file mode 100644
index 00000000..b5202b5b
--- /dev/null
+++ b/project-visibility-transition-guard/index.js
@@ -0,0 +1,292 @@
+const SEVERITY_WEIGHTS = {
+ critical: 36,
+ high: 22,
+ medium: 10,
+ low: 4
+};
+
+function addFinding(findings, severity, rule, message, action, refs = []) {
+ findings.push({ severity, rule, message, action, refs });
+}
+
+function collaboratorById(project) {
+ return new Map(project.collaborators.map((collaborator) => [collaborator.id, collaborator]));
+}
+
+function objectById(project) {
+ return new Map(project.objects.map((object) => [object.id, object]));
+}
+
+function activeHolds(project) {
+ return project.holds.filter((hold) => hold.status === "active");
+}
+
+function evaluateVisibilityTransition(project) {
+ const findings = [];
+ const collaborators = collaboratorById(project);
+ const objects = objectById(project);
+
+ if (project.workspace.currentVisibility === project.workspace.requestedVisibility) {
+ addFinding(
+ findings,
+ "low",
+ "visibility-transition-noop",
+ `Workspace is already ${project.workspace.requestedVisibility}.`,
+ "Skip transition processing and emit a no-op audit receipt.",
+ [project.workspace.id]
+ );
+ }
+
+ for (const role of project.policy.requiredApproverRoles) {
+ const approver = project.collaborators.find((collaborator) => collaborator.role === role);
+ if (!approver || approver.consent !== "approved") {
+ addFinding(
+ findings,
+ "critical",
+ "required-visibility-approver-missing",
+ `Required ${role} approval is not complete.`,
+ "Block public visibility until all required governance approvers consent.",
+ [role]
+ );
+ }
+ }
+
+ for (const collaborator of project.collaborators) {
+ if (project.policy.publicProfileRequiresConsent && !collaborator.profilePublic && collaborator.consent !== "approved") {
+ addFinding(
+ findings,
+ collaborator.role === "external-partner" ? "high" : "medium",
+ "profile-exposure-consent-missing",
+ `${collaborator.id} has no approved consent for public profile exposure.`,
+ "Hide the collaborator from public profile surfaces or collect explicit consent before transition.",
+ [collaborator.id, collaborator.role]
+ );
+ }
+ }
+
+ for (const object of project.objects) {
+ const sensitiveLabels = object.labels.filter((label) => project.policy.sensitiveLabels.includes(label));
+ if (sensitiveLabels.length > 0) {
+ addFinding(
+ findings,
+ "critical",
+ "sensitive-object-in-public-transition",
+ `${object.title} carries sensitive labels: ${sensitiveLabels.join(", ")}.`,
+ "Exclude or redact the object before public visibility is applied.",
+ [object.id, ...sensitiveLabels]
+ );
+ }
+
+ if (!project.policy.allowedPublicObjectKinds.includes(object.kind)) {
+ addFinding(
+ findings,
+ "high",
+ "object-kind-not-public-allowlisted",
+ `${object.kind} object ${object.id} is not allowlisted for public visibility.`,
+ "Map the object to a public-safe derivative or keep it private.",
+ [object.id, object.kind]
+ );
+ }
+
+ if (object.permission === "edit" || object.permission === "download") {
+ addFinding(
+ findings,
+ object.permission === "download" ? "critical" : "high",
+ "unsafe-public-object-permission",
+ `${object.id} would expose ${object.permission} permission after transition.`,
+ "Downgrade public permissions to read-only metadata or remove public access.",
+ [object.id, object.permission]
+ );
+ }
+
+ if (!object.publicReady) {
+ addFinding(
+ findings,
+ "high",
+ "object-not-public-ready",
+ `${object.title} is not marked public-ready.`,
+ "Require owner/steward readiness attestation before making the object public.",
+ [object.id]
+ );
+ }
+
+ if (!collaborators.has(object.ownerId)) {
+ addFinding(
+ findings,
+ "medium",
+ "object-owner-missing",
+ `${object.id} references missing owner ${object.ownerId}.`,
+ "Repair owner attribution before the public audit packet is emitted.",
+ [object.id, object.ownerId]
+ );
+ }
+ }
+
+ for (const hold of activeHolds(project)) {
+ for (const objectId of hold.objectIds) {
+ const object = objects.get(objectId);
+ addFinding(
+ findings,
+ "critical",
+ "active-hold-blocks-public-transition",
+ `${hold.kind} hold ${hold.id} blocks public exposure of ${object ? object.title : objectId}.`,
+ "Block the visibility transition until the hold expires or a documented waiver is attached.",
+ [hold.id, objectId, hold.expiresAt]
+ );
+ }
+ }
+
+ for (const invite of project.externalAccess) {
+ if (!collaborators.has(invite.collaboratorId)) {
+ addFinding(
+ findings,
+ "high",
+ "external-access-principal-unknown",
+ `External access ${invite.id} references unknown principal ${invite.collaboratorId}.`,
+ "Revoke or identify unknown external access before public transition.",
+ [invite.id, invite.collaboratorId]
+ );
+ }
+ if (invite.access === "download" && !invite.allowsRedistribution) {
+ addFinding(
+ findings,
+ "high",
+ "external-download-without-redistribution-rights",
+ `External access ${invite.id} allows downloads without redistribution rights.`,
+ "Downgrade or revoke external download grants before public visibility changes.",
+ [invite.id]
+ );
+ }
+ }
+
+ if (project.auditEvents.length < project.policy.minimumAuditEvents) {
+ addFinding(
+ findings,
+ "medium",
+ "visibility-audit-evidence-incomplete",
+ `Only ${project.auditEvents.length} audit events are present for the transition.`,
+ "Record requester, approver, object-review, and final decision events before applying visibility.",
+ [project.workspace.id]
+ );
+ }
+
+ const severitySummary = findings.reduce(
+ (summary, finding) => {
+ summary[finding.severity] += 1;
+ return summary;
+ },
+ { critical: 0, high: 0, medium: 0, low: 0 }
+ );
+ const score = Math.max(0, 100 - findings.reduce((sum, finding) => sum + SEVERITY_WEIGHTS[finding.severity], 0));
+
+ return { findings, severitySummary, score };
+}
+
+function decisionFromEvaluation(evaluation) {
+ if (evaluation.severitySummary.critical > 0) {
+ return "block-public-visibility-transition";
+ }
+ if (evaluation.severitySummary.high > 0 || evaluation.score < 75) {
+ return "hold-transition-for-governance-review";
+ }
+ if (evaluation.score < 90) {
+ return "manual-review-before-publication";
+ }
+ return "visibility-transition-ready";
+}
+
+function buildTransitionActions(findings) {
+ return findings.map((finding) => ({
+ priority: finding.severity === "critical" || finding.severity === "high" ? "blocking" : "review",
+ rule: finding.rule,
+ action: finding.action,
+ refs: finding.refs
+ }));
+}
+
+function buildReviewPacket(project) {
+ const evaluation = evaluateVisibilityTransition(project);
+ return {
+ guard: "project-visibility-transition-guard",
+ issue: "SCIBASE-AI/SCIBASE.AI#11",
+ workspaceId: project.workspace.id,
+ title: project.workspace.title,
+ currentVisibility: project.workspace.currentVisibility,
+ requestedVisibility: project.workspace.requestedVisibility,
+ asOfDate: project.asOfDate,
+ decision: decisionFromEvaluation(evaluation),
+ score: evaluation.score,
+ severitySummary: evaluation.severitySummary,
+ findings: evaluation.findings,
+ transitionActions: buildTransitionActions(evaluation.findings),
+ safety: [
+ "Synthetic project, collaborator, object, hold, access, and audit data only",
+ "No OAuth, SAML, ORCID, storage, profile, permission, email, or audit-log network calls",
+ "No private project data, credentials, human-subject records, live users, or access-control mutations"
+ ]
+ };
+}
+
+function renderMarkdownReport(packet) {
+ const lines = [
+ "# Project Visibility Transition Guard",
+ "",
+ `Workspace: ${packet.title}`,
+ `Issue: ${packet.issue}`,
+ `Transition: ${packet.currentVisibility} -> ${packet.requestedVisibility}`,
+ `Decision: ${packet.decision}`,
+ `Score: ${packet.score}`,
+ "",
+ "## Severity Summary",
+ "",
+ "| Severity | Count |",
+ "| --- | ---: |"
+ ];
+
+ for (const severity of ["critical", "high", "medium", "low"]) {
+ lines.push(`| ${severity} | ${packet.severitySummary[severity]} |`);
+ }
+
+ lines.push("", "## Findings", "");
+ for (const finding of packet.findings) {
+ lines.push(`- **${finding.severity} / ${finding.rule}**: ${finding.message}`);
+ lines.push(` - Action: ${finding.action}`);
+ lines.push(` - Refs: ${finding.refs.join(", ") || "none"}`);
+ }
+
+ lines.push("", "## Safety", "");
+ for (const item of packet.safety) {
+ lines.push(`- ${item}`);
+ }
+
+ return `${lines.join("\n")}\n`;
+}
+
+function renderSvgSummary(packet) {
+ const barWidth = Math.max(44, Math.min(760, packet.score * 7.6));
+ return `
+`;
+}
+
+module.exports = {
+ buildReviewPacket,
+ decisionFromEvaluation,
+ evaluateVisibilityTransition,
+ renderMarkdownReport,
+ renderSvgSummary
+};
diff --git a/project-visibility-transition-guard/package.json b/project-visibility-transition-guard/package.json
new file mode 100644
index 00000000..db67484f
--- /dev/null
+++ b/project-visibility-transition-guard/package.json
@@ -0,0 +1,14 @@
+{
+ "name": "project-visibility-transition-guard",
+ "version": "1.0.0",
+ "description": "Deterministic governance guard for scientific project visibility transitions.",
+ "main": "index.js",
+ "private": true,
+ "type": "commonjs",
+ "scripts": {
+ "check": "node --check index.js && node --check sample-data.js && node --check demo.js && node --check demo-video.js && node --check test.js",
+ "test": "node test.js",
+ "demo": "node demo.js",
+ "demo:video": "node demo-video.js"
+ }
+}
diff --git a/project-visibility-transition-guard/reports/demo.webm b/project-visibility-transition-guard/reports/demo.webm
new file mode 100644
index 0000000000000000000000000000000000000000..9cf0df5712ee79f36c0ebd8a3729bab10ddc9189
GIT binary patch
literal 12099
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