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 [
+ ``,
+ ""
+ ].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");