From 26322c03f5c4377bb8c0b0c6bca6a9c4ab9283da Mon Sep 17 00:00:00 2001 From: taherd <183945978+taherdhanera@users.noreply.github.com> Date: Fri, 22 May 2026 22:35:26 +0530 Subject: [PATCH] Add data availability statement guard --- .../README.md | 35 ++ .../demo-video.js | 173 ++++++++++ .../demo.js | 18 + .../index.js | 320 ++++++++++++++++++ .../package.json | 14 + .../reports/demo.webm | Bin 0 -> 29909 bytes .../reports/reviewer-packet.md | 51 +++ .../reports/summary.json | 188 ++++++++++ .../reports/summary.svg | 16 + .../requirements-map.md | 18 + .../sample-data.js | 106 ++++++ .../test.js | 70 ++++ 12 files changed, 1009 insertions(+) create mode 100644 collaborative-data-availability-statement-guard/README.md create mode 100644 collaborative-data-availability-statement-guard/demo-video.js create mode 100644 collaborative-data-availability-statement-guard/demo.js create mode 100644 collaborative-data-availability-statement-guard/index.js create mode 100644 collaborative-data-availability-statement-guard/package.json create mode 100644 collaborative-data-availability-statement-guard/reports/demo.webm create mode 100644 collaborative-data-availability-statement-guard/reports/reviewer-packet.md create mode 100644 collaborative-data-availability-statement-guard/reports/summary.json create mode 100644 collaborative-data-availability-statement-guard/reports/summary.svg create mode 100644 collaborative-data-availability-statement-guard/requirements-map.md create mode 100644 collaborative-data-availability-statement-guard/sample-data.js create mode 100644 collaborative-data-availability-statement-guard/test.js diff --git a/collaborative-data-availability-statement-guard/README.md b/collaborative-data-availability-statement-guard/README.md new file mode 100644 index 00000000..f7990e73 --- /dev/null +++ b/collaborative-data-availability-statement-guard/README.md @@ -0,0 +1,35 @@ +# Collaborative Data Availability Statement Guard + +Self-contained Real-time Collaborative Research Editor slice for +`SCIBASE-AI/SCIBASE.AI#12`. + +The guard evaluates whether a collaborative manuscript can be exported with +complete data and code availability statements. It checks required availability +sections, repository accessions, statement citations, dataset/code licenses, +reviewer-only access windows, de-identification evidence for human-derived +material, role-based approvals, blocking comments, and unmerged editor changes. + +This is intentionally separate from reference-library merging, notification +visibility, accessibility, presence privacy, evidence binding, and general +embargo-release workflows. Its job is to gate the final manuscript export when +the availability statement and linked artifact evidence are not review-ready. + +## 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 repository hosts, journal +systems, identity services, storage APIs, email systems, or live manuscript +export services. diff --git a/collaborative-data-availability-statement-guard/demo-video.js b/collaborative-data-availability-statement-guard/demo-video.js new file mode 100644 index 00000000..3298eb2c --- /dev/null +++ b/collaborative-data-availability-statement-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` + + + + Collaborative data availability statement guard demo + + + + +
recording
+ + +`; + +fs.mkdirSync(reportDir, { recursive: true }); + +const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "data-availability-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/collaborative-data-availability-statement-guard/demo.js b/collaborative-data-availability-statement-guard/demo.js new file mode 100644 index 00000000..97c3730c --- /dev/null +++ b/collaborative-data-availability-statement-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/collaborative-data-availability-statement-guard/index.js b/collaborative-data-availability-statement-guard/index.js new file mode 100644 index 00000000..be1a95c9 --- /dev/null +++ b/collaborative-data-availability-statement-guard/index.js @@ -0,0 +1,320 @@ +const SEVERITY_WEIGHTS = { + critical: 36, + high: 22, + medium: 11, + low: 4 +}; + +function daysBetween(a, b) { + const left = new Date(a).getTime(); + const right = new Date(b).getTime(); + return Math.floor((right - left) / (24 * 60 * 60 * 1000)); +} + +function normalize(value) { + return String(value || "").trim().toLowerCase(); +} + +function addFinding(findings, severity, rule, message, action, refs = []) { + findings.push({ severity, rule, message, action, refs }); +} + +function sectionById(manuscript) { + return new Map(manuscript.sections.map((section) => [section.id, section])); +} + +function accessionPattern(kind) { + if (kind === "code") { + return /^(GH|GL|DOI)-[A-Za-z0-9._/-]+$/; + } + return /^(ZEN|DRYAD|FIGSHARE|OSF|DOI)-[A-Za-z0-9._/-]+$/; +} + +function referencedAccessions(manuscript) { + return new Set(manuscript.citations.map((citation) => normalize(citation.accessionId)).filter(Boolean)); +} + +function evaluateStatementReadiness(project) { + const findings = []; + const sections = sectionById(project.manuscript); + const citationAccessions = referencedAccessions(project.manuscript); + + for (const sectionId of project.policy.requiredSections) { + const section = sections.get(sectionId); + if (!section || normalize(section.text).length < 40) { + addFinding( + findings, + "critical", + "missing-required-availability-section", + `Required section ${sectionId} is missing or too thin for export.`, + "Block manuscript export until the collaborative editor contains a complete availability statement.", + [sectionId] + ); + } + } + + for (const citation of project.manuscript.citations) { + if (!sections.has(citation.sectionId)) { + addFinding( + findings, + "high", + "citation-anchor-missing", + `Citation ${citation.id} points at missing section ${citation.sectionId}.`, + "Repair the citation anchor before generating the final manuscript package.", + [citation.id, citation.sectionId] + ); + } + } + + for (const repository of project.repositories) { + const accession = normalize(repository.accessionId); + if (!accession) { + addFinding( + findings, + "critical", + "repository-accession-missing", + `${repository.label} has no stable accession or repository identifier.`, + "Attach a stable repository accession or mark the material as non-distributable with reviewer evidence.", + [repository.id] + ); + } else if (!accessionPattern(repository.kind).test(repository.accessionId)) { + addFinding( + findings, + "high", + "repository-accession-format-invalid", + `${repository.label} has accession ${repository.accessionId}, which does not match accepted export formats.`, + "Normalize the repository identifier before export so readers can resolve the artifact.", + [repository.id, repository.accessionId] + ); + } else if (!citationAccessions.has(accession)) { + addFinding( + findings, + "medium", + "repository-not-mentioned-in-statement", + `${repository.label} is registered but not cited in the availability sections.`, + "Add the repository accession to the data or code availability statement.", + [repository.id, repository.accessionId] + ); + } + + if (repository.kind === "dataset" && !project.policy.acceptedDatasetLicenses.includes(repository.license)) { + addFinding( + findings, + "high", + "dataset-license-missing-or-unaccepted", + `${repository.label} has dataset license ${repository.license || "none"}.`, + "Add an accepted dataset license or document why restricted access is required.", + [repository.id] + ); + } + + if (repository.kind === "code" && !project.policy.acceptedCodeLicenses.includes(repository.license)) { + addFinding( + findings, + "high", + "code-license-missing-or-unaccepted", + `${repository.label} has code license ${repository.license || "none"}.`, + "Add an accepted code license before exposing reproducibility controls.", + [repository.id] + ); + } + + if (repository.containsHumanDerivedData && !repository.deidentificationEvidenceId) { + addFinding( + findings, + "critical", + "human-derived-data-without-deidentification-evidence", + `${repository.label} contains human-derived material without de-identification evidence.`, + "Block export until the data steward links de-identification or restriction evidence.", + [repository.id] + ); + } + + if (repository.access === "embargoed" && repository.releaseDate) { + const releaseLag = daysBetween(project.manuscript.exportDeadline, repository.releaseDate); + if (releaseLag > project.policy.publicReleaseGraceDays) { + addFinding( + findings, + "medium", + "embargo-release-lags-export", + `${repository.label} releases ${releaseLag} days after the manuscript export deadline.`, + "Confirm the target journal accepts this availability timing before final export.", + [repository.id, repository.releaseDate] + ); + } + } + + if (repository.access !== "public" && repository.reviewerLinkExpiresAt) { + const reviewerDays = daysBetween(project.asOfDate, repository.reviewerLinkExpiresAt); + if (reviewerDays < project.policy.reviewerLinkMinimumDays) { + addFinding( + findings, + "high", + "reviewer-link-expires-before-review-window", + `${repository.label} reviewer access expires in ${reviewerDays} days.`, + "Refresh reviewer-only links before export so peer reviewers can inspect restricted artifacts.", + [repository.id, repository.reviewerLinkExpiresAt] + ); + } + } + } + + for (const role of project.policy.requiredApproverRoles) { + const approver = project.collaborators.find((collaborator) => collaborator.role === role); + if (!approver || approver.approval !== "approved") { + addFinding( + findings, + "high", + "required-availability-approver-missing", + `Required ${role} approval is not complete.`, + "Hold export until all role-based collaborators approve the availability statement.", + [role] + ); + } + } + + for (const comment of project.editorState.unresolvedComments) { + if (comment.severity === "blocking") { + addFinding( + findings, + "high", + "blocking-availability-comment-open", + `Blocking comment ${comment.id} remains open on ${comment.sectionId}.`, + "Resolve blocking availability comments before final manuscript export.", + [comment.id, comment.sectionId] + ); + } + } + + for (const change of project.editorState.pendingChanges) { + if (change.status !== "merged") { + addFinding( + findings, + "medium", + "availability-change-unmerged", + `Pending change ${change.id} in ${change.sectionId} has not been merged.`, + "Merge, reject, or explicitly defer the collaborative availability edit before export.", + [change.id, change.sectionId] + ); + } + } + + 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-export-until-availability-evidence-is-clean"; + } + if (evaluation.score < 75 || evaluation.severitySummary.high > 0) { + return "hold-export-for-availability-review"; + } + if (evaluation.score < 90) { + return "manual-review-before-export"; + } + return "availability-statement-ready"; +} + +function buildReviewerActions(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 = evaluateStatementReadiness(project); + return { + guard: "collaborative-data-availability-statement-guard", + issue: "SCIBASE-AI/SCIBASE.AI#12", + manuscriptId: project.manuscript.id, + title: project.manuscript.title, + targetJournal: project.manuscript.targetJournal, + asOfDate: project.asOfDate, + decision: decisionFromEvaluation(evaluation), + score: evaluation.score, + severitySummary: evaluation.severitySummary, + findings: evaluation.findings, + reviewerActions: buildReviewerActions(evaluation.findings), + safety: [ + "Synthetic manuscript, repository, collaborator, and review data only", + "No GitHub, Zenodo, journal, identity, storage, or email network calls", + "No private manuscript content, human-subject records, credentials, or live export mutations" + ] + }; +} + +function renderMarkdownReport(packet) { + const lines = [ + "# Collaborative Data Availability Statement Guard", + "", + `Manuscript: ${packet.title}`, + `Issue: ${packet.issue}`, + `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)); + const totalFindings = packet.findings.length; + return ` + + Data Availability Statement Guard + ${packet.title} + + ${packet.decision} + Findings ${totalFindings} | Critical ${packet.severitySummary.critical} | High ${packet.severitySummary.high} + + Export readiness score + + + ${packet.score}/100 + + Collaborative editor export checkpoint + Validates sections, repository IDs, licenses, reviewer links, approvals, comments, and pending edits. + +`; +} + +module.exports = { + buildReviewPacket, + decisionFromEvaluation, + evaluateStatementReadiness, + renderMarkdownReport, + renderSvgSummary +}; diff --git a/collaborative-data-availability-statement-guard/package.json b/collaborative-data-availability-statement-guard/package.json new file mode 100644 index 00000000..bc525426 --- /dev/null +++ b/collaborative-data-availability-statement-guard/package.json @@ -0,0 +1,14 @@ +{ + "name": "collaborative-data-availability-statement-guard", + "version": "1.0.0", + "description": "Deterministic export guard for collaborative manuscript data and code availability statements.", + "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/collaborative-data-availability-statement-guard/reports/demo.webm b/collaborative-data-availability-statement-guard/reports/demo.webm new file mode 100644 index 0000000000000000000000000000000000000000..7b86aeca6579ec8e37f9650e4d9658559c74af10 GIT binary patch literal 29909 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 literal 0 HcmV?d00001 diff --git a/collaborative-data-availability-statement-guard/reports/reviewer-packet.md b/collaborative-data-availability-statement-guard/reports/reviewer-packet.md new file mode 100644 index 00000000..b7c5bd68 --- /dev/null +++ b/collaborative-data-availability-statement-guard/reports/reviewer-packet.md @@ -0,0 +1,51 @@ +# Collaborative Data Availability Statement Guard + +Manuscript: Joint Galaxy Morphology Review +Issue: SCIBASE-AI/SCIBASE.AI#12 +Decision: block-export-until-availability-evidence-is-clean +Score: 0 + +## Severity Summary + +| Severity | Count | +| --- | ---: | +| critical | 2 | +| high | 5 | +| medium | 2 | +| low | 0 | + +## Findings + +- **medium / embargo-release-lags-export**: Processed synthetic tables releases 11 days after the manuscript export deadline. + - Action: Confirm the target journal accepts this availability timing before final export. + - Refs: repo-data-processed, 2026-06-05 +- **high / reviewer-link-expires-before-review-window**: Processed synthetic tables reviewer access expires in 1 days. + - Action: Refresh reviewer-only links before export so peer reviewers can inspect restricted artifacts. + - Refs: repo-data-processed, 2026-05-23 +- **critical / repository-accession-missing**: Reviewer note extracts has no stable accession or repository identifier. + - Action: Attach a stable repository accession or mark the material as non-distributable with reviewer evidence. + - Refs: repo-data-raw +- **high / dataset-license-missing-or-unaccepted**: Reviewer note extracts has dataset license none. + - Action: Add an accepted dataset license or document why restricted access is required. + - Refs: repo-data-raw +- **critical / human-derived-data-without-deidentification-evidence**: Reviewer note extracts contains human-derived material without de-identification evidence. + - Action: Block export until the data steward links de-identification or restriction evidence. + - Refs: repo-data-raw +- **high / reviewer-link-expires-before-review-window**: Reviewer note extracts reviewer access expires in -3 days. + - Action: Refresh reviewer-only links before export so peer reviewers can inspect restricted artifacts. + - Refs: repo-data-raw, 2026-05-19 +- **high / required-availability-approver-missing**: Required data-steward approval is not complete. + - Action: Hold export until all role-based collaborators approve the availability statement. + - Refs: data-steward +- **high / blocking-availability-comment-open**: Blocking comment comment-17 remains open on data-availability. + - Action: Resolve blocking availability comments before final manuscript export. + - Refs: comment-17, data-availability +- **medium / availability-change-unmerged**: Pending change change-9 in data-availability has not been merged. + - Action: Merge, reject, or explicitly defer the collaborative availability edit before export. + - Refs: change-9, data-availability + +## Safety + +- Synthetic manuscript, repository, collaborator, and review data only +- No GitHub, Zenodo, journal, identity, storage, or email network calls +- No private manuscript content, human-subject records, credentials, or live export mutations diff --git a/collaborative-data-availability-statement-guard/reports/summary.json b/collaborative-data-availability-statement-guard/reports/summary.json new file mode 100644 index 00000000..50ca7593 --- /dev/null +++ b/collaborative-data-availability-statement-guard/reports/summary.json @@ -0,0 +1,188 @@ +{ + "guard": "collaborative-data-availability-statement-guard", + "issue": "SCIBASE-AI/SCIBASE.AI#12", + "manuscriptId": "ms-collab-astro-042", + "title": "Joint Galaxy Morphology Review", + "targetJournal": "Synthetic Journal of Open Science", + "asOfDate": "2026-05-22", + "decision": "block-export-until-availability-evidence-is-clean", + "score": 0, + "severitySummary": { + "critical": 2, + "high": 5, + "medium": 2, + "low": 0 + }, + "findings": [ + { + "severity": "medium", + "rule": "embargo-release-lags-export", + "message": "Processed synthetic tables releases 11 days after the manuscript export deadline.", + "action": "Confirm the target journal accepts this availability timing before final export.", + "refs": [ + "repo-data-processed", + "2026-06-05" + ] + }, + { + "severity": "high", + "rule": "reviewer-link-expires-before-review-window", + "message": "Processed synthetic tables reviewer access expires in 1 days.", + "action": "Refresh reviewer-only links before export so peer reviewers can inspect restricted artifacts.", + "refs": [ + "repo-data-processed", + "2026-05-23" + ] + }, + { + "severity": "critical", + "rule": "repository-accession-missing", + "message": "Reviewer note extracts has no stable accession or repository identifier.", + "action": "Attach a stable repository accession or mark the material as non-distributable with reviewer evidence.", + "refs": [ + "repo-data-raw" + ] + }, + { + "severity": "high", + "rule": "dataset-license-missing-or-unaccepted", + "message": "Reviewer note extracts has dataset license none.", + "action": "Add an accepted dataset license or document why restricted access is required.", + "refs": [ + "repo-data-raw" + ] + }, + { + "severity": "critical", + "rule": "human-derived-data-without-deidentification-evidence", + "message": "Reviewer note extracts contains human-derived material without de-identification evidence.", + "action": "Block export until the data steward links de-identification or restriction evidence.", + "refs": [ + "repo-data-raw" + ] + }, + { + "severity": "high", + "rule": "reviewer-link-expires-before-review-window", + "message": "Reviewer note extracts reviewer access expires in -3 days.", + "action": "Refresh reviewer-only links before export so peer reviewers can inspect restricted artifacts.", + "refs": [ + "repo-data-raw", + "2026-05-19" + ] + }, + { + "severity": "high", + "rule": "required-availability-approver-missing", + "message": "Required data-steward approval is not complete.", + "action": "Hold export until all role-based collaborators approve the availability statement.", + "refs": [ + "data-steward" + ] + }, + { + "severity": "high", + "rule": "blocking-availability-comment-open", + "message": "Blocking comment comment-17 remains open on data-availability.", + "action": "Resolve blocking availability comments before final manuscript export.", + "refs": [ + "comment-17", + "data-availability" + ] + }, + { + "severity": "medium", + "rule": "availability-change-unmerged", + "message": "Pending change change-9 in data-availability has not been merged.", + "action": "Merge, reject, or explicitly defer the collaborative availability edit before export.", + "refs": [ + "change-9", + "data-availability" + ] + } + ], + "reviewerActions": [ + { + "priority": "review", + "rule": "embargo-release-lags-export", + "action": "Confirm the target journal accepts this availability timing before final export.", + "refs": [ + "repo-data-processed", + "2026-06-05" + ] + }, + { + "priority": "blocking", + "rule": "reviewer-link-expires-before-review-window", + "action": "Refresh reviewer-only links before export so peer reviewers can inspect restricted artifacts.", + "refs": [ + "repo-data-processed", + "2026-05-23" + ] + }, + { + "priority": "blocking", + "rule": "repository-accession-missing", + "action": "Attach a stable repository accession or mark the material as non-distributable with reviewer evidence.", + "refs": [ + "repo-data-raw" + ] + }, + { + "priority": "blocking", + "rule": "dataset-license-missing-or-unaccepted", + "action": "Add an accepted dataset license or document why restricted access is required.", + "refs": [ + "repo-data-raw" + ] + }, + { + "priority": "blocking", + "rule": "human-derived-data-without-deidentification-evidence", + "action": "Block export until the data steward links de-identification or restriction evidence.", + "refs": [ + "repo-data-raw" + ] + }, + { + "priority": "blocking", + "rule": "reviewer-link-expires-before-review-window", + "action": "Refresh reviewer-only links before export so peer reviewers can inspect restricted artifacts.", + "refs": [ + "repo-data-raw", + "2026-05-19" + ] + }, + { + "priority": "blocking", + "rule": "required-availability-approver-missing", + "action": "Hold export until all role-based collaborators approve the availability statement.", + "refs": [ + "data-steward" + ] + }, + { + "priority": "blocking", + "rule": "blocking-availability-comment-open", + "action": "Resolve blocking availability comments before final manuscript export.", + "refs": [ + "comment-17", + "data-availability" + ] + }, + { + "priority": "review", + "rule": "availability-change-unmerged", + "action": "Merge, reject, or explicitly defer the collaborative availability edit before export.", + "refs": [ + "change-9", + "data-availability" + ] + } + ], + "safety": [ + "Synthetic manuscript, repository, collaborator, and review data only", + "No GitHub, Zenodo, journal, identity, storage, or email network calls", + "No private manuscript content, human-subject records, credentials, or live export mutations" + ] +} diff --git a/collaborative-data-availability-statement-guard/reports/summary.svg b/collaborative-data-availability-statement-guard/reports/summary.svg new file mode 100644 index 00000000..623c4d01 --- /dev/null +++ b/collaborative-data-availability-statement-guard/reports/summary.svg @@ -0,0 +1,16 @@ + + + Data Availability Statement Guard + Joint Galaxy Morphology Review + + block-export-until-availability-evidence-is-clean + Findings 9 | Critical 2 | High 5 + + Export readiness score + + + 0/100 + + Collaborative editor export checkpoint + Validates sections, repository IDs, licenses, reviewer links, approvals, comments, and pending edits. + diff --git a/collaborative-data-availability-statement-guard/requirements-map.md b/collaborative-data-availability-statement-guard/requirements-map.md new file mode 100644 index 00000000..4b73c06a --- /dev/null +++ b/collaborative-data-availability-statement-guard/requirements-map.md @@ -0,0 +1,18 @@ +# Requirements Map + +Issue: `SCIBASE-AI/SCIBASE.AI#12` + +| Issue requirement | Implementation | +| --- | --- | +| Real-time collaborative research editor | Models collaborative manuscript sections, comments, pending changes, and role-based author approvals before export. | +| Manuscript export readiness | Blocks final export when availability statements, repository citations, reviewer access, or collaborator approvals are incomplete. | +| Research artifact handling | Validates dataset and code repository accessions, artifact licenses, checksums, and human-derived data safeguards. | +| Reviewer-safe workflow | Produces deterministic JSON, Markdown, SVG, and video artifacts for local reviewer inspection without external services. | +| Non-overlap with existing slices | Focuses on availability statement export gating rather than reference merges, notifications, accessibility, presence, evidence binding, or general embargo automation. | +| Safe local validation | Includes dependency-free tests and demo generation from synthetic manuscript and repository metadata only. | + +## Non-goals + +- No live GitHub, Zenodo, OSF, journal, identity, storage, or email calls. +- No private manuscripts, credentials, human-subject records, or reviewer data. +- No mutation of collaborative editor documents or repository permissions. diff --git a/collaborative-data-availability-statement-guard/sample-data.js b/collaborative-data-availability-statement-guard/sample-data.js new file mode 100644 index 00000000..c9df16a1 --- /dev/null +++ b/collaborative-data-availability-statement-guard/sample-data.js @@ -0,0 +1,106 @@ +const project = { + asOfDate: "2026-05-22", + manuscript: { + id: "ms-collab-astro-042", + title: "Joint Galaxy Morphology Review", + targetJournal: "Synthetic Journal of Open Science", + exportDeadline: "2026-05-25", + sections: [ + { + id: "methods", + title: "Methods", + text: + "The study used a synthetic morphology corpus with reviewer-only raw tables and a scripted notebook pipeline." + }, + { + id: "data-availability", + title: "Data Availability", + text: + "Processed tables are available at Zenodo accession ZEN-2026-7788 after acceptance. Raw human-derived review notes are restricted and released as de-identified summaries only." + }, + { + id: "code-availability", + title: "Code Availability", + text: + "Analysis notebooks are available at GitHub repository synthetic-lab/galaxy-review under the MIT license." + } + ], + citations: [ + { id: "cite-dataset", kind: "dataset", accessionId: "ZEN-2026-7788", sectionId: "data-availability" }, + { id: "cite-notebook", kind: "code", accessionId: "GH-synthetic-lab-galaxy-review", sectionId: "code-availability" } + ] + }, + collaborators: [ + { id: "alice", role: "corresponding-author", approval: "approved", approvedAt: "2026-05-20" }, + { id: "devon", role: "data-steward", approval: "pending", approvedAt: null }, + { id: "mira", role: "code-owner", approval: "approved", approvedAt: "2026-05-21" } + ], + editorState: { + lastExportAttemptAt: "2026-05-22T15:20:00Z", + unresolvedComments: [ + { + id: "comment-17", + sectionId: "data-availability", + severity: "blocking", + text: "Confirm reviewer-only link expiry before export." + } + ], + pendingChanges: [ + { id: "change-9", sectionId: "data-availability", authorId: "devon", status: "unmerged" } + ] + }, + repositories: [ + { + id: "repo-data-processed", + label: "Processed synthetic tables", + kind: "dataset", + accessionId: "ZEN-2026-7788", + provider: "Zenodo", + access: "embargoed", + releaseDate: "2026-06-05", + reviewerLinkExpiresAt: "2026-05-23", + license: "CC-BY-4.0", + containsHumanDerivedData: false, + deidentificationEvidenceId: null, + checksum: "sha256:3e1a9bff5781" + }, + { + id: "repo-data-raw", + label: "Reviewer note extracts", + kind: "dataset", + accessionId: "", + provider: "Institutional vault", + access: "restricted", + releaseDate: null, + reviewerLinkExpiresAt: "2026-05-19", + license: "", + containsHumanDerivedData: true, + deidentificationEvidenceId: "", + checksum: "sha256:rawnotes9" + }, + { + id: "repo-code-notebooks", + label: "Notebook pipeline", + kind: "code", + accessionId: "GH-synthetic-lab-galaxy-review", + provider: "GitHub", + access: "public", + releaseDate: "2026-05-20", + reviewerLinkExpiresAt: null, + license: "MIT", + containsHumanDerivedData: false, + deidentificationEvidenceId: null, + checksum: "sha256:notebook221" + } + ], + policy: { + requiredSections: ["data-availability", "code-availability"], + requiredApproverRoles: ["corresponding-author", "data-steward", "code-owner"], + acceptedDatasetLicenses: ["CC-BY-4.0", "CC0-1.0", "ODC-BY-1.0"], + acceptedCodeLicenses: ["MIT", "Apache-2.0", "BSD-3-Clause"], + reviewerLinkMinimumDays: 5, + publicReleaseGraceDays: 7 + } +}; + +module.exports = { project }; diff --git a/collaborative-data-availability-statement-guard/test.js b/collaborative-data-availability-statement-guard/test.js new file mode 100644 index 00000000..23874cad --- /dev/null +++ b/collaborative-data-availability-statement-guard/test.js @@ -0,0 +1,70 @@ +const assert = require("assert"); +const { project } = require("./sample-data"); +const { + buildReviewPacket, + evaluateStatementReadiness, + renderMarkdownReport, + renderSvgSummary +} = require("./index"); + +const evaluation = evaluateStatementReadiness(project); +const packet = buildReviewPacket(project); + +assert.strictEqual(packet.guard, "collaborative-data-availability-statement-guard"); +assert.strictEqual(packet.issue, "SCIBASE-AI/SCIBASE.AI#12"); +assert.strictEqual(packet.decision, "block-export-until-availability-evidence-is-clean"); +assert.ok(evaluation.score < 70, "expected low readiness score for blocked export"); + +assert.ok( + evaluation.findings.some((finding) => finding.rule === "repository-accession-missing"), + "expected missing repository accession finding" +); +assert.ok( + evaluation.findings.some((finding) => finding.rule === "human-derived-data-without-deidentification-evidence"), + "expected human-derived data finding" +); +assert.ok( + evaluation.findings.some((finding) => finding.rule === "reviewer-link-expires-before-review-window"), + "expected reviewer link expiry finding" +); +assert.ok( + evaluation.findings.some((finding) => finding.rule === "required-availability-approver-missing"), + "expected missing approver finding" +); +assert.ok( + evaluation.findings.some((finding) => finding.rule === "blocking-availability-comment-open"), + "expected blocking comment finding" +); + +const cleanProject = JSON.parse(JSON.stringify(project)); +cleanProject.collaborators.find((collaborator) => collaborator.role === "data-steward").approval = "approved"; +cleanProject.collaborators.find((collaborator) => collaborator.role === "data-steward").approvedAt = "2026-05-22"; +cleanProject.editorState.unresolvedComments = []; +cleanProject.editorState.pendingChanges = []; +cleanProject.repositories.find((repository) => repository.id === "repo-data-processed").releaseDate = "2026-05-29"; +cleanProject.repositories.find((repository) => repository.id === "repo-data-processed").reviewerLinkExpiresAt = "2026-06-10"; +const rawRepo = cleanProject.repositories.find((repository) => repository.id === "repo-data-raw"); +rawRepo.accessionId = "OSF-raw-review-notes-restricted"; +rawRepo.license = "CC-BY-4.0"; +rawRepo.reviewerLinkExpiresAt = "2026-06-10"; +rawRepo.deidentificationEvidenceId = "deid-attestation-2026-05"; +cleanProject.manuscript.citations.push({ + id: "cite-raw-restricted", + kind: "dataset", + accessionId: "OSF-raw-review-notes-restricted", + sectionId: "data-availability" +}); + +const cleanPacket = buildReviewPacket(cleanProject); +assert.strictEqual(cleanPacket.decision, "availability-statement-ready"); +assert.strictEqual(cleanPacket.findings.length, 0); + +const markdown = renderMarkdownReport(packet); +assert.ok(markdown.includes("## Findings")); +assert.ok(markdown.includes("repository-accession-missing")); + +const svg = renderSvgSummary(packet); +assert.ok(svg.includes("