From 0d429b53833a7152f7e3e1aee6ee761502b59f4f Mon Sep 17 00:00:00 2001 From: AlonePenguin <187998801+AlonePenguin@users.noreply.github.com> Date: Mon, 1 Jun 2026 08:06:35 -0400 Subject: [PATCH] Add enterprise style package provenance guard --- .../README.md | 18 + .../demo.js | 50 +++ .../index.js | 356 ++++++++++++++++++ .../make-demo-video.js | 91 +++++ .../package.json | 21 ++ .../reports/clean-style-package.json | 111 ++++++ .../reports/demo-script.txt | 10 + .../reports/demo.mp4 | Bin 0 -> 10330 bytes .../reports/risky-style-package.json | 292 ++++++++++++++ .../style-package-provenance-report.md | 39 ++ .../reports/summary.svg | 8 + .../sample-data.js | 161 ++++++++ .../test.js | 31 ++ 13 files changed, 1188 insertions(+) create mode 100644 enterprise-style-package-provenance-guard/README.md create mode 100644 enterprise-style-package-provenance-guard/demo.js create mode 100644 enterprise-style-package-provenance-guard/index.js create mode 100644 enterprise-style-package-provenance-guard/make-demo-video.js create mode 100644 enterprise-style-package-provenance-guard/package.json create mode 100644 enterprise-style-package-provenance-guard/reports/clean-style-package.json create mode 100644 enterprise-style-package-provenance-guard/reports/demo-script.txt create mode 100644 enterprise-style-package-provenance-guard/reports/demo.mp4 create mode 100644 enterprise-style-package-provenance-guard/reports/risky-style-package.json create mode 100644 enterprise-style-package-provenance-guard/reports/style-package-provenance-report.md create mode 100644 enterprise-style-package-provenance-guard/reports/summary.svg create mode 100644 enterprise-style-package-provenance-guard/sample-data.js create mode 100644 enterprise-style-package-provenance-guard/test.js diff --git a/enterprise-style-package-provenance-guard/README.md b/enterprise-style-package-provenance-guard/README.md new file mode 100644 index 00000000..a51cb97d --- /dev/null +++ b/enterprise-style-package-provenance-guard/README.md @@ -0,0 +1,18 @@ +# Enterprise Style Package Provenance Guard + +This module adds a focused Enterprise Tooling export-pipeline guard for journal and funder formatting plugins. It validates synthetic style packages before institution-scale JATS, DOCX, or LaTeX exports are released. + +The guard checks approved plugin versions, required export-format coverage, template checksum parity, citation-style parity, DOI/ORCID/version-history preservation, validation recency, reviewer signoff, generated output provenance digests, and private field leakage in style previews. + +## Commands + +```bash +npm run check +npm test +npm run demo +npm run verify-video +``` + +`npm run demo` writes reviewer artifacts to `reports/`, including JSON packets, a Markdown report, an SVG summary, and a short H.264 MP4 demo. + +Synthetic data only. No live repositories, journal systems, funder portals, credentials, private manuscripts, external APIs, payment systems, or payout-account settings are used. diff --git a/enterprise-style-package-provenance-guard/demo.js b/enterprise-style-package-provenance-guard/demo.js new file mode 100644 index 00000000..487a114e --- /dev/null +++ b/enterprise-style-package-provenance-guard/demo.js @@ -0,0 +1,50 @@ +const fs = require("node:fs"); +const path = require("node:path"); +const { evaluateStylePackage, renderMarkdownReport, renderSvgSummary } = require("./index"); +const { cleanPacket, riskyPacket } = require("./sample-data"); + +const reportsDir = path.join(__dirname, "reports"); +fs.mkdirSync(reportsDir, { recursive: true }); + +const cleanEvaluation = evaluateStylePackage(cleanPacket); +const riskyEvaluation = evaluateStylePackage(riskyPacket); + +fs.writeFileSync( + path.join(reportsDir, "clean-style-package.json"), + `${JSON.stringify({ input: cleanPacket, evaluation: cleanEvaluation }, null, 2)}\n` +); +fs.writeFileSync( + path.join(reportsDir, "risky-style-package.json"), + `${JSON.stringify({ input: riskyPacket, evaluation: riskyEvaluation }, null, 2)}\n` +); +fs.writeFileSync( + path.join(reportsDir, "style-package-provenance-report.md"), + renderMarkdownReport(riskyPacket, riskyEvaluation) +); +fs.writeFileSync( + path.join(reportsDir, "summary.svg"), + renderSvgSummary(riskyEvaluation) +); +fs.writeFileSync( + path.join(reportsDir, "demo-script.txt"), + [ + "Enterprise style package provenance guard demo", + "", + `Clean packet decision: ${cleanEvaluation.summary.decision}`, + `Clean audit digest: ${cleanEvaluation.summary.auditDigest}`, + "", + `Risky packet decision: ${riskyEvaluation.summary.decision}`, + `Risky finding count: ${riskyEvaluation.summary.findingCount}`, + `Risky audit digest: ${riskyEvaluation.summary.auditDigest}`, + "", + "The risky packet demonstrates a formatting plugin drift before JATS/DOCX/LaTeX export: unapproved plugin version, missing formats, checksum drift, citation mismatch, stale validation, missing signoff, metadata preservation gaps, and private preview-field leakage.", + "" + ].join("\n") +); + +console.log(JSON.stringify({ + cleanDecision: cleanEvaluation.summary.decision, + riskyDecision: riskyEvaluation.summary.decision, + riskyFindings: riskyEvaluation.summary.findingCount, + report: "reports/style-package-provenance-report.md" +}, null, 2)); diff --git a/enterprise-style-package-provenance-guard/index.js b/enterprise-style-package-provenance-guard/index.js new file mode 100644 index 00000000..13905813 --- /dev/null +++ b/enterprise-style-package-provenance-guard/index.js @@ -0,0 +1,356 @@ +const crypto = require("node:crypto"); + +function asArray(value) { + return Array.isArray(value) ? value : []; +} + +function stableJson(value) { + if (Array.isArray(value)) { + return `[${value.map(stableJson).join(",")}]`; + } + if (value && typeof value === "object") { + return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${stableJson(value[key])}`).join(",")}}`; + } + return JSON.stringify(value); +} + +function sha256(value) { + return crypto.createHash("sha256").update(stableJson(value)).digest("hex"); +} + +function toDate(value) { + const parsed = new Date(value || ""); + return Number.isNaN(parsed.getTime()) ? null : parsed; +} + +function daysBetween(laterValue, earlierValue) { + const later = toDate(laterValue); + const earlier = toDate(earlierValue); + if (!later || !earlier) { + return null; + } + return Math.floor((later.getTime() - earlier.getTime()) / (24 * 60 * 60 * 1000)); +} + +function severityRank(severity) { + return { critical: 4, high: 3, medium: 2, low: 1 }[severity] || 0; +} + +function addFinding(findings, severity, code, message, refs, action) { + findings.push({ + severity, + code, + message, + refs: asArray(refs), + action + }); +} + +function isSemver(value) { + return /^\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?$/.test(String(value || "")); +} + +function normalizedSet(values) { + return new Set(asArray(values).map((value) => String(value).trim().toLowerCase()).filter(Boolean)); +} + +function setDifference(required, actual) { + const actualSet = normalizedSet(actual); + return asArray(required).filter((value) => !actualSet.has(String(value).trim().toLowerCase())); +} + +function evaluateStylePackage(packet) { + const findings = []; + const reviewDate = packet.reviewDate || new Date().toISOString().slice(0, 10); + const stylePackages = asArray(packet.stylePackages); + const approvedPlugins = new Map(asArray(packet.approvedPlugins).map((plugin) => [plugin.id, plugin])); + const requiredFormats = asArray(packet.requiredExportFormats); + const packageSummaries = []; + + if (stylePackages.length === 0) { + addFinding( + findings, + "critical", + "STYLE_PACKAGE_EMPTY", + "No enterprise formatting style packages were supplied for export review.", + [packet.institutionId || "institution"], + "attach_style_packages_before_export_release" + ); + } + + for (const stylePackage of stylePackages) { + const refs = [stylePackage.id || "style-package"]; + const plugin = approvedPlugins.get(stylePackage.pluginId); + const pluginVersion = String(stylePackage.pluginVersion || ""); + const target = stylePackage.target || {}; + + if (!plugin) { + addFinding( + findings, + "critical", + "PLUGIN_NOT_APPROVED", + `${stylePackage.id || "style package"} uses plugin ${stylePackage.pluginId || "unknown"} without enterprise approval.`, + refs, + "route_plugin_for_enterprise_approval" + ); + } + + if (!isSemver(pluginVersion)) { + addFinding( + findings, + "high", + "PLUGIN_VERSION_NOT_PINNED", + `${stylePackage.id || "style package"} is missing a pinned semantic plugin version.`, + refs, + "pin_plugin_version_before_export" + ); + } else if (plugin && !asArray(plugin.allowedVersions).includes(pluginVersion)) { + addFinding( + findings, + "high", + "PLUGIN_VERSION_NOT_APPROVED", + `${stylePackage.id || "style package"} uses ${pluginVersion}, which is not in the approved plugin version list.`, + refs, + "promote_or_replace_approved_plugin_version" + ); + } + + const missingFormats = setDifference(requiredFormats, stylePackage.formats); + if (missingFormats.length > 0) { + addFinding( + findings, + "high", + "EXPORT_FORMAT_COVERAGE_GAP", + `${stylePackage.id || "style package"} is missing required export formats: ${missingFormats.join(", ")}.`, + refs, + "add_required_export_format_templates" + ); + } + + const templateChecks = asArray(stylePackage.templates); + if (templateChecks.length === 0) { + addFinding( + findings, + "high", + "TEMPLATE_MANIFEST_MISSING", + `${stylePackage.id || "style package"} has no template checksum manifest.`, + refs, + "attach_template_checksum_manifest" + ); + } + + for (const template of templateChecks) { + if (!template.expectedChecksum || !template.actualChecksum) { + addFinding( + findings, + "medium", + "TEMPLATE_CHECKSUM_INCOMPLETE", + `${template.path || stylePackage.id || "template"} lacks both expected and actual checksums.`, + refs, + "record_expected_and_actual_template_checksums" + ); + } else if (template.expectedChecksum !== template.actualChecksum) { + addFinding( + findings, + "critical", + "TEMPLATE_CHECKSUM_DRIFT", + `${template.path || stylePackage.id || "template"} checksum differs from the approved style package manifest.`, + refs, + "block_export_until_template_is_rebuilt_from_approved_source" + ); + } + } + + if (String(stylePackage.citationStyle || "").toLowerCase() !== String(target.citationStyle || "").toLowerCase()) { + addFinding( + findings, + "high", + "CITATION_STYLE_MISMATCH", + `${stylePackage.id || "style package"} uses ${stylePackage.citationStyle || "unknown"} citations for ${target.name || "target"} which requires ${target.citationStyle || "unknown"}.`, + refs, + "select_target_approved_citation_style" + ); + } + + const metadata = stylePackage.metadataPreservation || {}; + const missingMetadata = [ + ["DOI", metadata.doi], + ["ORCID", metadata.orcid], + ["version history", metadata.versionHistory], + ["license", metadata.license], + ["funder award", metadata.funderAward] + ].filter(([, present]) => present !== true).map(([label]) => label); + if (missingMetadata.length > 0) { + addFinding( + findings, + "high", + "EXPORT_METADATA_PRESERVATION_GAP", + `${stylePackage.id || "style package"} does not preserve ${missingMetadata.join(", ")} metadata across export formats.`, + refs, + "preserve_required_publication_metadata" + ); + } + + const validationAge = daysBetween(reviewDate, stylePackage.lastValidatedAt); + if (validationAge === null || validationAge > Number(packet.maxValidationAgeDays || 90)) { + addFinding( + findings, + "medium", + "STYLE_VALIDATION_STALE", + `${stylePackage.id || "style package"} style validation is ${validationAge === null ? "missing" : `${validationAge} days old`}.`, + refs, + "rerun_style_export_validation" + ); + } + + const signoff = stylePackage.reviewerSignoff || {}; + if (!signoff.reviewer || signoff.status !== "approved") { + addFinding( + findings, + "high", + "STYLE_SIGNOFF_MISSING", + `${stylePackage.id || "style package"} lacks approved enterprise reviewer signoff.`, + refs, + "obtain_enterprise_style_reviewer_signoff" + ); + } + + const generatedOutputs = asArray(stylePackage.generatedOutputs); + const missingOutputDigest = generatedOutputs.filter((output) => !output.provenanceDigest || !output.builtFromTemplateChecksum); + if (missingOutputDigest.length > 0) { + addFinding( + findings, + "medium", + "OUTPUT_PROVENANCE_DIGEST_MISSING", + `${stylePackage.id || "style package"} has ${missingOutputDigest.length} generated outputs without build provenance digests.`, + refs, + "record_output_build_provenance_digests" + ); + } + + if (asArray(stylePackage.previewFields).some((field) => field.private === true && field.exported === true)) { + addFinding( + findings, + "critical", + "PRIVATE_FIELD_EXPORTED_IN_STYLE_PREVIEW", + `${stylePackage.id || "style package"} exports private internal style-preview fields.`, + refs, + "remove_private_preview_fields_from_export" + ); + } + + packageSummaries.push({ + id: stylePackage.id, + pluginId: stylePackage.pluginId, + pluginVersion, + target: target.name, + formatCount: asArray(stylePackage.formats).length, + templateCount: templateChecks.length, + validationAgeDays: validationAge, + outputCount: generatedOutputs.length + }); + } + + findings.sort((a, b) => severityRank(b.severity) - severityRank(a.severity) || a.code.localeCompare(b.code)); + const decision = findings.some((finding) => severityRank(finding.severity) >= 4) + ? "hold_enterprise_style_exports" + : findings.some((finding) => severityRank(finding.severity) >= 3) + ? "revise_style_package_before_export" + : findings.some((finding) => finding.severity === "medium") + ? "release_after_style_validation_refresh" + : "release_enterprise_style_exports"; + + const summary = { + institutionId: packet.institutionId, + reviewDate, + decision, + stylePackagesReviewed: stylePackages.length, + findingCount: findings.length, + highOrCriticalFindings: findings.filter((finding) => severityRank(finding.severity) >= 3).length + }; + const auditDigest = `sha256:${sha256({ summary, findings, packageSummaries }).slice(0, 16)}`; + + return { + summary: { + ...summary, + auditDigest + }, + findings, + packageSummaries, + requiredExportFormats: requiredFormats + }; +} + +function renderMarkdownReport(packet, evaluation) { + const lines = [ + `# Enterprise Style Package Provenance Report`, + "", + `Institution: ${packet.institutionId}`, + `Review date: ${evaluation.summary.reviewDate}`, + `Decision: ${evaluation.summary.decision}`, + `Audit digest: ${evaluation.summary.auditDigest}`, + "", + "## Style Packages", + "", + "| Package | Target | Plugin | Formats | Templates | Validation age |", + "| --- | --- | --- | ---: | ---: | ---: |" + ]; + + for (const item of evaluation.packageSummaries) { + lines.push(`| ${item.id} | ${item.target || "unknown"} | ${item.pluginId}@${item.pluginVersion || "unknown"} | ${item.formatCount} | ${item.templateCount} | ${item.validationAgeDays === null ? "missing" : `${item.validationAgeDays}d`} |`); + } + + lines.push("", "## Findings", ""); + if (evaluation.findings.length === 0) { + lines.push("No findings."); + } else { + for (const finding of evaluation.findings) { + lines.push(`- **${finding.severity.toUpperCase()} ${finding.code}**: ${finding.message} Action: \`${finding.action}\`.`); + } + } + + lines.push("", "## Required Export Formats", ""); + for (const format of evaluation.requiredExportFormats) { + lines.push(`- ${format}`); + } + lines.push(""); + return lines.join("\n"); +} + +function renderSvgSummary(evaluation) { + const width = 920; + const height = 420; + const critical = evaluation.findings.filter((finding) => finding.severity === "critical").length; + const high = evaluation.findings.filter((finding) => finding.severity === "high").length; + const medium = evaluation.findings.filter((finding) => finding.severity === "medium").length; + const bars = [ + ["critical", critical, "#b91c1c"], + ["high", high, "#dc2626"], + ["medium", medium, "#d97706"] + ]; + const barSvg = bars.map(([label, count, color], index) => { + const y = 150 + index * 72; + const barWidth = Math.max(16, Math.min(520, count * 54)); + return `${label}${count}`; + }).join(""); + + return [ + ``, + ``, + ``, + `Enterprise Style Package Provenance`, + `Decision: ${evaluation.summary.decision}`, + barSvg, + `Audit ${evaluation.summary.auditDigest}`, + ``, + "" + ].join("\n"); +} + +module.exports = { + evaluateStylePackage, + renderMarkdownReport, + renderSvgSummary, + stableJson, + sha256 +}; diff --git a/enterprise-style-package-provenance-guard/make-demo-video.js b/enterprise-style-package-provenance-guard/make-demo-video.js new file mode 100644 index 00000000..167891f6 --- /dev/null +++ b/enterprise-style-package-provenance-guard/make-demo-video.js @@ -0,0 +1,91 @@ +const fs = require("node:fs"); +const path = require("node:path"); +const { spawnSync } = require("node:child_process"); +const { evaluateStylePackage } = require("./index"); +const { cleanPacket, riskyPacket } = require("./sample-data"); + +const reportsDir = path.join(__dirname, "reports"); +const framesDir = path.join(reportsDir, "frames"); +fs.mkdirSync(framesDir, { recursive: true }); + +const clean = evaluateStylePackage(cleanPacket); +const risky = evaluateStylePackage(riskyPacket); +const width = 960; +const height = 540; +const frames = 72; +const fps = 18; + +function setPixel(buffer, x, y, r, g, b) { + if (x < 0 || y < 0 || x >= width || y >= height) { + return; + } + const offset = (y * width + x) * 3; + buffer[offset] = r; + buffer[offset + 1] = g; + buffer[offset + 2] = b; +} + +function fillRect(buffer, x, y, w, h, r, g, b) { + for (let row = y; row < y + h; row += 1) { + for (let col = x; col < x + w; col += 1) { + setPixel(buffer, col, row, r, g, b); + } + } +} + +function writeFrame(index, progress) { + const buffer = Buffer.alloc(width * height * 3, 250); + fillRect(buffer, 0, 0, width, height, 248, 250, 252); + fillRect(buffer, 56, 48, 848, 444, 255, 255, 255); + fillRect(buffer, 56, 48, 848, 8, 17, 24, 39); + + const cleanWidth = Math.floor(328 * Math.min(1, progress * 1.7)); + const riskyWidth = Math.floor(328 * Math.max(0, (progress - 0.18) * 1.45)); + fillRect(buffer, 104, 116, 328, 64, 226, 232, 240); + fillRect(buffer, 104, 116, cleanWidth, 64, 22, 163, 74); + fillRect(buffer, 528, 116, 328, 64, 226, 232, 240); + fillRect(buffer, 528, 116, riskyWidth, 64, 220, 38, 38); + + for (let i = 0; i < clean.summary.stylePackagesReviewed; i += 1) { + fillRect(buffer, 128 + i * 84, 246, 56, 94, 16, 185, 129); + fillRect(buffer, 136 + i * 84, 256, 40, 18, 255, 255, 255); + fillRect(buffer, 136 + i * 84, 286, 40, 10, 255, 255, 255); + } + + for (let i = 0; i < Math.min(12, risky.summary.findingCount); i += 1) { + const barHeight = 30 + (i % 6) * 18; + fillRect(buffer, 548 + i * 24, 386 - barHeight, 18, barHeight, 185, 28, 28); + } + + fillRect(buffer, 104, 424, Math.floor(744 * progress), 18, 37, 99, 235); + fillRect(buffer, 104, 454, Math.floor(560 * progress), 18, 217, 119, 6); + + const header = Buffer.from(`P6\n${width} ${height}\n255\n`, "ascii"); + fs.writeFileSync(path.join(framesDir, `frame-${String(index).padStart(3, "0")}.ppm`), Buffer.concat([header, buffer])); +} + +for (let index = 0; index < frames; index += 1) { + writeFrame(index, index / (frames - 1)); +} + +const output = path.join(reportsDir, "demo.mp4"); +const result = spawnSync(process.env.FFMPEG_PATH || "ffmpeg", [ + "-y", + "-framerate", + String(fps), + "-i", + path.join(framesDir, "frame-%03d.ppm"), + "-pix_fmt", + "yuv420p", + "-movflags", + "+faststart", + output +], { stdio: "inherit" }); + +fs.rmSync(framesDir, { recursive: true, force: true }); + +if (result.status !== 0) { + process.exit(result.status || 1); +} + +console.log(`Wrote ${output}`); diff --git a/enterprise-style-package-provenance-guard/package.json b/enterprise-style-package-provenance-guard/package.json new file mode 100644 index 00000000..03267324 --- /dev/null +++ b/enterprise-style-package-provenance-guard/package.json @@ -0,0 +1,21 @@ +{ + "name": "enterprise-style-package-provenance-guard", + "version": "1.0.0", + "private": true, + "description": "Synthetic enterprise export formatting-plugin provenance guard for SCIBASE enterprise tooling.", + "main": "index.js", + "scripts": { + "check": "node --check index.js && node --check sample-data.js && node --check test.js && node --check demo.js && node --check make-demo-video.js", + "test": "node test.js", + "demo": "node demo.js && node make-demo-video.js", + "verify-video": "ffprobe -v error -select_streams v:0 -show_entries stream=codec_name,width,height,duration,avg_frame_rate -show_entries format=duration,size -of default=noprint_wrappers=1 reports/demo.mp4" + }, + "keywords": [ + "scibase", + "enterprise-tooling", + "export-pipeline", + "formatting-plugin", + "provenance" + ], + "license": "MIT" +} diff --git a/enterprise-style-package-provenance-guard/reports/clean-style-package.json b/enterprise-style-package-provenance-guard/reports/clean-style-package.json new file mode 100644 index 00000000..82f860d8 --- /dev/null +++ b/enterprise-style-package-provenance-guard/reports/clean-style-package.json @@ -0,0 +1,111 @@ +{ + "input": { + "institutionId": "northstar-research-office", + "reviewDate": "2026-06-01", + "maxValidationAgeDays": 90, + "requiredExportFormats": [ + "JATS", + "DOCX", + "LaTeX" + ], + "approvedPlugins": [ + { + "id": "journal-style-pack", + "allowedVersions": [ + "3.4.2", + "3.4.3" + ], + "owner": "enterprise-publication-ops" + } + ], + "stylePackages": [ + { + "id": "cell-reports-2026-style", + "pluginId": "journal-style-pack", + "pluginVersion": "3.4.3", + "target": { + "name": "Cell Reports", + "citationStyle": "vancouver" + }, + "formats": [ + "JATS", + "DOCX", + "LaTeX" + ], + "citationStyle": "vancouver", + "lastValidatedAt": "2026-05-20", + "reviewerSignoff": { + "reviewer": "pubops-style-reviewer", + "status": "approved" + }, + "templates": [ + { + "path": "templates/cell-reports/article.jats.xml", + "expectedChecksum": "sha256:6bd2f4c0c2a6", + "actualChecksum": "sha256:6bd2f4c0c2a6" + }, + { + "path": "templates/cell-reports/manuscript.docx", + "expectedChecksum": "sha256:a9f28f1de342", + "actualChecksum": "sha256:a9f28f1de342" + } + ], + "metadataPreservation": { + "doi": true, + "orcid": true, + "versionHistory": true, + "license": true, + "funderAward": true + }, + "generatedOutputs": [ + { + "path": "exports/cell-reports/article.xml", + "builtFromTemplateChecksum": "sha256:6bd2f4c0c2a6", + "provenanceDigest": "sha256:output-9122" + }, + { + "path": "exports/cell-reports/manuscript.docx", + "builtFromTemplateChecksum": "sha256:a9f28f1de342", + "provenanceDigest": "sha256:output-8227" + } + ], + "previewFields": [ + { + "name": "publicTitle", + "exported": true, + "private": false + } + ] + } + ] + }, + "evaluation": { + "summary": { + "institutionId": "northstar-research-office", + "reviewDate": "2026-06-01", + "decision": "release_enterprise_style_exports", + "stylePackagesReviewed": 1, + "findingCount": 0, + "highOrCriticalFindings": 0, + "auditDigest": "sha256:3a3a13df45782064" + }, + "findings": [], + "packageSummaries": [ + { + "id": "cell-reports-2026-style", + "pluginId": "journal-style-pack", + "pluginVersion": "3.4.3", + "target": "Cell Reports", + "formatCount": 3, + "templateCount": 2, + "validationAgeDays": 12, + "outputCount": 2 + } + ], + "requiredExportFormats": [ + "JATS", + "DOCX", + "LaTeX" + ] + } +} diff --git a/enterprise-style-package-provenance-guard/reports/demo-script.txt b/enterprise-style-package-provenance-guard/reports/demo-script.txt new file mode 100644 index 00000000..d29b2711 --- /dev/null +++ b/enterprise-style-package-provenance-guard/reports/demo-script.txt @@ -0,0 +1,10 @@ +Enterprise style package provenance guard demo + +Clean packet decision: release_enterprise_style_exports +Clean audit digest: sha256:3a3a13df45782064 + +Risky packet decision: hold_enterprise_style_exports +Risky finding count: 17 +Risky audit digest: sha256:e519c9e6fe7731dd + +The risky packet demonstrates a formatting plugin drift before JATS/DOCX/LaTeX export: unapproved plugin version, missing formats, checksum drift, citation mismatch, stale validation, missing signoff, metadata preservation gaps, and private preview-field leakage. diff --git a/enterprise-style-package-provenance-guard/reports/demo.mp4 b/enterprise-style-package-provenance-guard/reports/demo.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..c3717323248bdb0d9d8d7f39309ba17a3f5319ff GIT binary patch literal 10330 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 + + +Enterprise Style Package Provenance +Decision: hold_enterprise_style_exports +critical3high10medium4 +Audit sha256:e519c9e6fe7731dd + diff --git a/enterprise-style-package-provenance-guard/sample-data.js b/enterprise-style-package-provenance-guard/sample-data.js new file mode 100644 index 00000000..8cf3a188 --- /dev/null +++ b/enterprise-style-package-provenance-guard/sample-data.js @@ -0,0 +1,161 @@ +const cleanPacket = { + institutionId: "northstar-research-office", + reviewDate: "2026-06-01", + maxValidationAgeDays: 90, + requiredExportFormats: ["JATS", "DOCX", "LaTeX"], + approvedPlugins: [ + { + id: "journal-style-pack", + allowedVersions: ["3.4.2", "3.4.3"], + owner: "enterprise-publication-ops" + } + ], + stylePackages: [ + { + id: "cell-reports-2026-style", + pluginId: "journal-style-pack", + pluginVersion: "3.4.3", + target: { + name: "Cell Reports", + citationStyle: "vancouver" + }, + formats: ["JATS", "DOCX", "LaTeX"], + citationStyle: "vancouver", + lastValidatedAt: "2026-05-20", + reviewerSignoff: { + reviewer: "pubops-style-reviewer", + status: "approved" + }, + templates: [ + { + path: "templates/cell-reports/article.jats.xml", + expectedChecksum: "sha256:6bd2f4c0c2a6", + actualChecksum: "sha256:6bd2f4c0c2a6" + }, + { + path: "templates/cell-reports/manuscript.docx", + expectedChecksum: "sha256:a9f28f1de342", + actualChecksum: "sha256:a9f28f1de342" + } + ], + metadataPreservation: { + doi: true, + orcid: true, + versionHistory: true, + license: true, + funderAward: true + }, + generatedOutputs: [ + { + path: "exports/cell-reports/article.xml", + builtFromTemplateChecksum: "sha256:6bd2f4c0c2a6", + provenanceDigest: "sha256:output-9122" + }, + { + path: "exports/cell-reports/manuscript.docx", + builtFromTemplateChecksum: "sha256:a9f28f1de342", + provenanceDigest: "sha256:output-8227" + } + ], + previewFields: [ + { + name: "publicTitle", + exported: true, + private: false + } + ] + } + ] +}; + +const riskyPacket = { + institutionId: "northstar-research-office", + reviewDate: "2026-06-01", + maxValidationAgeDays: 90, + requiredExportFormats: ["JATS", "DOCX", "LaTeX"], + approvedPlugins: [ + { + id: "journal-style-pack", + allowedVersions: ["3.4.2"], + owner: "enterprise-publication-ops" + } + ], + stylePackages: [ + { + id: "horizon-eu-repository-style", + pluginId: "journal-style-pack", + pluginVersion: "3.5.0", + target: { + name: "Horizon EU grant portal", + citationStyle: "apa" + }, + formats: ["DOCX"], + citationStyle: "vancouver", + lastValidatedAt: "2025-12-10", + reviewerSignoff: { + reviewer: "", + status: "draft" + }, + templates: [ + { + path: "templates/horizon/report.docx", + expectedChecksum: "sha256:approved-4411", + actualChecksum: "sha256:local-drift-7719" + }, + { + path: "templates/horizon/article.jats.xml", + expectedChecksum: "", + actualChecksum: "sha256:missing-expected" + } + ], + metadataPreservation: { + doi: true, + orcid: false, + versionHistory: false, + license: true, + funderAward: false + }, + generatedOutputs: [ + { + path: "exports/horizon/report.docx", + builtFromTemplateChecksum: "", + provenanceDigest: "" + } + ], + previewFields: [ + { + name: "internalReviewerNotes", + exported: true, + private: true + } + ] + }, + { + id: "unapproved-preprint-style", + pluginId: "community-style-snapshot", + pluginVersion: "latest", + target: { + name: "bioRxiv", + citationStyle: "vancouver" + }, + formats: ["JATS", "DOCX"], + citationStyle: "vancouver", + lastValidatedAt: null, + reviewerSignoff: null, + templates: [], + metadataPreservation: { + doi: false, + orcid: false, + versionHistory: false, + license: false, + funderAward: false + }, + generatedOutputs: [] + } + ] +}; + +module.exports = { + cleanPacket, + riskyPacket +}; diff --git a/enterprise-style-package-provenance-guard/test.js b/enterprise-style-package-provenance-guard/test.js new file mode 100644 index 00000000..2ede4c32 --- /dev/null +++ b/enterprise-style-package-provenance-guard/test.js @@ -0,0 +1,31 @@ +const assert = require("node:assert/strict"); +const { evaluateStylePackage, sha256 } = require("./index"); +const { cleanPacket, riskyPacket } = require("./sample-data"); + +const clean = evaluateStylePackage(cleanPacket); +assert.equal(clean.summary.decision, "release_enterprise_style_exports"); +assert.equal(clean.summary.findingCount, 0); +assert.equal(clean.summary.stylePackagesReviewed, 1); +assert.ok(clean.summary.auditDigest.startsWith("sha256:")); + +const risky = evaluateStylePackage(riskyPacket); +assert.equal(risky.summary.decision, "hold_enterprise_style_exports"); +assert.equal(risky.summary.stylePackagesReviewed, 2); +assert.ok(risky.summary.findingCount >= 10); +assert.ok(risky.summary.highOrCriticalFindings >= 7); + +const findingCodes = new Set(risky.findings.map((finding) => finding.code)); +assert.ok(findingCodes.has("TEMPLATE_CHECKSUM_DRIFT")); +assert.ok(findingCodes.has("PRIVATE_FIELD_EXPORTED_IN_STYLE_PREVIEW")); +assert.ok(findingCodes.has("PLUGIN_NOT_APPROVED")); +assert.ok(findingCodes.has("PLUGIN_VERSION_NOT_PINNED")); +assert.ok(findingCodes.has("EXPORT_METADATA_PRESERVATION_GAP")); +assert.ok(findingCodes.has("CITATION_STYLE_MISMATCH")); +assert.ok(findingCodes.has("EXPORT_FORMAT_COVERAGE_GAP")); + +const firstDigest = evaluateStylePackage(riskyPacket).summary.auditDigest; +const secondDigest = evaluateStylePackage(riskyPacket).summary.auditDigest; +assert.equal(firstDigest, secondDigest); +assert.equal(sha256({ b: 2, a: 1 }), sha256({ a: 1, b: 2 })); + +console.log("enterprise style package provenance guard tests passed");