diff --git a/institutional-repository-sync-sla-guard/demo.js b/institutional-repository-sync-sla-guard/demo.js
new file mode 100644
index 00000000..7157512a
--- /dev/null
+++ b/institutional-repository-sync-sla-guard/demo.js
@@ -0,0 +1,69 @@
+const fs = require("fs");
+const path = require("path");
+
+const { evaluateSyncJobs } = require("./index");
+const { syncJobs } = require("./sample-data");
+
+const reportDir = path.join(__dirname, "reports");
+fs.mkdirSync(reportDir, { recursive: true });
+
+const report = evaluateSyncJobs(syncJobs);
+fs.writeFileSync(path.join(reportDir, "repository-sync-sla-report.json"), `${JSON.stringify(report, null, 2)}\n`);
+
+const lines = [
+ "# Institutional Repository Sync SLA Guard",
+ "",
+ `Generated: ${report.generatedAt}`,
+ `Report digest: \`${report.reportDigest}\``,
+ "",
+ "## Summary",
+ "",
+ `- Total jobs: ${report.summary.total}`,
+ `- Ready: ${report.summary.ready}`,
+ `- Needs review: ${report.summary.needs_review}`,
+ `- Blocked: ${report.summary.blocked}`,
+ `- Findings: ${report.summary.findingCount}`,
+ "",
+ "## Decisions",
+ "",
+];
+
+for (const decision of report.decisions) {
+ lines.push(`### ${decision.id}`);
+ lines.push("");
+ lines.push(`- Decision: **${decision.decision}**`);
+ lines.push(`- Project: ${decision.project}`);
+ lines.push(`- Target: ${decision.targetRepository} (${decision.targetType})`);
+ lines.push(`- Escalation: \`${decision.escalation}\``);
+ lines.push(`- Audit digest: \`${decision.auditDigest}\``);
+ lines.push("- Findings:");
+ const findings = decision.findings.length
+ ? decision.findings
+ : [{ severity: "info", code: "none", message: "No blocking or review findings." }];
+ for (const finding of findings) {
+ lines.push(` - ${finding.severity}: ${finding.code} - ${finding.message}`);
+ }
+ lines.push("");
+}
+if (lines[lines.length - 1] === "") {
+ lines.pop();
+}
+fs.writeFileSync(path.join(reportDir, "repository-sync-sla-report.md"), `${lines.join("\n")}\n`);
+
+const svg = `
+`;
+fs.writeFileSync(path.join(reportDir, "summary.svg"), svg);
+
+console.log(JSON.stringify(report.summary, null, 2));
diff --git a/institutional-repository-sync-sla-guard/index.js b/institutional-repository-sync-sla-guard/index.js
new file mode 100644
index 00000000..21b0df80
--- /dev/null
+++ b/institutional-repository-sync-sla-guard/index.js
@@ -0,0 +1,131 @@
+const crypto = require("crypto");
+
+function digest(value) {
+ return crypto.createHash("sha256").update(JSON.stringify(value)).digest("hex");
+}
+
+function embargoDays(embargoUntil, now = new Date("2026-05-29T00:00:00Z")) {
+ if (!embargoUntil) return 0;
+ const embargoDate = new Date(`${embargoUntil}T00:00:00Z`);
+ if (Number.isNaN(embargoDate.getTime())) return 0;
+ return Math.ceil((embargoDate.getTime() - now.getTime()) / 86_400_000);
+}
+
+function evaluateJob(job) {
+ const findings = [];
+
+ if (job.mandate.dueInHours < 0) {
+ findings.push({
+ severity: "blocker",
+ code: "mandate-deadline-missed",
+ message: `${job.mandate.funder} repository sync is ${Math.abs(job.mandate.dueInHours)} hour(s) overdue.`,
+ });
+ } else if (job.mandate.dueInHours <= 48) {
+ findings.push({
+ severity: "review",
+ code: "mandate-deadline-near",
+ message: `${job.mandate.funder} deadline is within ${job.mandate.dueInHours} hour(s).`,
+ });
+ }
+
+ if (!job.package.metadataComplete) {
+ findings.push({
+ severity: "blocker",
+ code: "metadata-incomplete",
+ message: "Repository export metadata is incomplete.",
+ });
+ }
+
+ if (job.mandate.requiresDoi && !job.package.doiPresent) {
+ findings.push({
+ severity: "blocker",
+ code: "missing-doi",
+ message: "Target repository or funder mandate requires DOI preservation.",
+ });
+ }
+
+ if (job.mandate.requiresOrcid && job.package.orcidCoveragePercent < 90) {
+ findings.push({
+ severity: "review",
+ code: "low-orcid-coverage",
+ message: `ORCID coverage is ${job.package.orcidCoveragePercent}%, below the 90% admin threshold.`,
+ });
+ }
+
+ if (!job.package.artifactHashPinned) {
+ findings.push({
+ severity: "blocker",
+ code: "unpinned-artifact-hash",
+ message: "Export package must pin artifact hashes before repository sync.",
+ });
+ }
+
+ const holdDays = embargoDays(job.package.embargoUntil);
+ if (holdDays > 0) {
+ findings.push({
+ severity: "blocker",
+ code: "active-embargo",
+ message: `Repository sync is blocked by an active embargo for ${holdDays} day(s).`,
+ });
+ }
+
+ if (job.operations.queuedHours >= 24 || job.operations.retryCount >= 3) {
+ findings.push({
+ severity: "review",
+ code: "sync-backlog-risk",
+ message: `Sync has queued for ${job.operations.queuedHours} hour(s) with ${job.operations.retryCount} retry attempt(s).`,
+ });
+ }
+
+ if (job.operations.lastWebhookStatus === "failed") {
+ findings.push({
+ severity: "review",
+ code: "webhook-delivery-failed",
+ message: "Last institutional webhook delivery failed and requires admin visibility.",
+ });
+ }
+
+ const blockers = findings.filter((finding) => finding.severity === "blocker");
+ const reviews = findings.filter((finding) => finding.severity === "review");
+ const decision = blockers.length ? "blocked" : reviews.length ? "needs_review" : "ready";
+
+ return {
+ id: job.id,
+ project: job.project,
+ targetRepository: job.target.repository,
+ targetType: job.target.type,
+ adminOwner: job.operations.adminOwner,
+ decision,
+ findings,
+ escalation: decision === "ready" ? "release-to-sync-worker" : `route-to-${job.operations.adminOwner}`,
+ auditDigest: digest({
+ id: job.id,
+ decision,
+ findings: findings.map((finding) => finding.code),
+ target: job.target,
+ mandate: job.mandate,
+ }),
+ };
+}
+
+function evaluateSyncJobs(jobs) {
+ const decisions = jobs.map(evaluateJob);
+ const summary = decisions.reduce(
+ (acc, decision) => {
+ acc.total += 1;
+ acc[decision.decision] += 1;
+ acc.findingCount += decision.findings.length;
+ return acc;
+ },
+ { total: 0, ready: 0, needs_review: 0, blocked: 0, findingCount: 0 }
+ );
+ return {
+ generatedAt: "2026-05-29T00:00:00.000Z",
+ guard: "institutional-repository-sync-sla-guard",
+ summary,
+ decisions,
+ reportDigest: digest({ summary, decisions }),
+ };
+}
+
+module.exports = { evaluateJob, evaluateSyncJobs, embargoDays };
diff --git a/institutional-repository-sync-sla-guard/readme.md b/institutional-repository-sync-sla-guard/readme.md
new file mode 100644
index 00000000..99ecd64d
--- /dev/null
+++ b/institutional-repository-sync-sla-guard/readme.md
@@ -0,0 +1,27 @@
+# Institutional Repository Sync SLA Guard
+
+This is a focused Enterprise Tooling slice for SCIBASE export pipelines. It evaluates institutional repository sync jobs before they are released to external systems such as Zenodo, PubMed Central, arXiv, Invenio, DSpace, funder portals, or institutional archives.
+
+The module uses synthetic reviewer scenarios only. It does not call external repositories, APIs, webhooks, identity providers, payment systems, live projects, private user data, credentials, or tokens.
+
+## What It Checks
+
+- Funder mandate deadlines and overdue sync jobs.
+- Metadata completeness, DOI preservation, ORCID coverage, and artifact hash pins.
+- Embargo holds before export.
+- Backlog/retry risk and webhook delivery visibility for enterprise admins.
+- Deterministic audit digests and escalation routes.
+
+## Run
+
+```bash
+node institutional-repository-sync-sla-guard/test.js
+node institutional-repository-sync-sla-guard/demo.js
+node institutional-repository-sync-sla-guard/render-video.js
+```
+
+Generated reviewer artifacts are written to `institutional-repository-sync-sla-guard/reports/`.
+
+## Scope Boundary
+
+This slice is distinct from existing #19 work around enterprise dashboards, institutional admin auditing, generic API/webhook integrations, API rate limiting, webhook delivery, accessibility, repository/archive export, artifact hosting, and broad integration modules. It focuses specifically on SLA and compliance governance for institutional repository sync backlogs.
diff --git a/institutional-repository-sync-sla-guard/render-video.js b/institutional-repository-sync-sla-guard/render-video.js
new file mode 100644
index 00000000..89ce11e5
--- /dev/null
+++ b/institutional-repository-sync-sla-guard/render-video.js
@@ -0,0 +1,73 @@
+const { execFileSync } = require("child_process");
+const fs = require("fs");
+const path = require("path");
+
+const { evaluateSyncJobs } = require("./index");
+const { syncJobs } = require("./sample-data");
+
+const reportDir = path.join(__dirname, "reports");
+const framePath = path.join(reportDir, "demo-frame.ppm");
+const mp4Path = path.join(reportDir, "demo.mp4");
+const report = evaluateSyncJobs(syncJobs);
+
+function rgb(hex) {
+ return [
+ Number.parseInt(hex.slice(0, 2), 16),
+ Number.parseInt(hex.slice(2, 4), 16),
+ Number.parseInt(hex.slice(4, 6), 16),
+ ];
+}
+
+function rect(buffer, width, x, y, w, h, color) {
+ const [r, g, b] = rgb(color);
+ for (let row = y; row < y + h; row += 1) {
+ for (let col = x; col < x + w; col += 1) {
+ const idx = (row * width + col) * 3;
+ buffer[idx] = r;
+ buffer[idx + 1] = g;
+ buffer[idx + 2] = b;
+ }
+ }
+}
+
+const width = 960;
+const height = 540;
+const pixels = Buffer.alloc(width * height * 3);
+for (let i = 0; i < pixels.length; i += 3) {
+ pixels[i] = 0xee;
+ pixels[i + 1] = 0xf3;
+ pixels[i + 2] = 0xf7;
+}
+rect(pixels, width, 54, 52, 852, 436, "ffffff");
+rect(pixels, width, 54, 52, 852, 4, "1f3349");
+rect(pixels, width, 54, 484, 852, 4, "1f3349");
+rect(pixels, width, 54, 52, 4, 436, "1f3349");
+rect(pixels, width, 902, 52, 4, 436, "1f3349");
+rect(pixels, width, 96, 180, Math.max(1, report.summary.ready) * 120, 58, "2f855a");
+rect(pixels, width, 96, 270, Math.max(1, report.summary.needs_review) * 120, 58, "b7791f");
+rect(pixels, width, 96, 360, Math.max(1, report.summary.blocked) * 120, 58, "b83232");
+rect(pixels, width, 96, 450, 720, 12, "42566f");
+fs.writeFileSync(framePath, Buffer.concat([Buffer.from(`P6\n${width} ${height}\n255\n`), pixels]));
+
+execFileSync(
+ "ffmpeg",
+ [
+ "-y",
+ "-loop",
+ "1",
+ "-framerate",
+ "24",
+ "-i",
+ framePath,
+ "-t",
+ "5",
+ "-vf",
+ "format=yuv420p",
+ "-movflags",
+ "+faststart",
+ mp4Path,
+ ],
+ { stdio: "inherit" }
+);
+
+console.log(mp4Path);
diff --git a/institutional-repository-sync-sla-guard/reports/demo.mp4 b/institutional-repository-sync-sla-guard/reports/demo.mp4
new file mode 100644
index 00000000..1f1f4499
Binary files /dev/null and b/institutional-repository-sync-sla-guard/reports/demo.mp4 differ
diff --git a/institutional-repository-sync-sla-guard/reports/repository-sync-sla-report.json b/institutional-repository-sync-sla-guard/reports/repository-sync-sla-report.json
new file mode 100644
index 00000000..df10cfd6
--- /dev/null
+++ b/institutional-repository-sync-sla-guard/reports/repository-sync-sla-report.json
@@ -0,0 +1,96 @@
+{
+ "generatedAt": "2026-05-29T00:00:00.000Z",
+ "guard": "institutional-repository-sync-sla-guard",
+ "summary": {
+ "total": 4,
+ "ready": 1,
+ "needs_review": 0,
+ "blocked": 3,
+ "findingCount": 7
+ },
+ "decisions": [
+ {
+ "id": "zenodo-open-access-overdue",
+ "project": "Single-cell reproducibility atlas",
+ "targetRepository": "Zenodo",
+ "targetType": "indexed-repository",
+ "adminOwner": "research-office",
+ "decision": "blocked",
+ "findings": [
+ {
+ "severity": "blocker",
+ "code": "mandate-deadline-missed",
+ "message": "Horizon EU repository sync is 18 hour(s) overdue."
+ },
+ {
+ "severity": "review",
+ "code": "sync-backlog-risk",
+ "message": "Sync has queued for 42 hour(s) with 5 retry attempt(s)."
+ },
+ {
+ "severity": "review",
+ "code": "webhook-delivery-failed",
+ "message": "Last institutional webhook delivery failed and requires admin visibility."
+ }
+ ],
+ "escalation": "route-to-research-office",
+ "auditDigest": "036ab26e31a2ab0b83954569ca3998b69bb6cd2bce0093b3cd974220502ac3b4"
+ },
+ {
+ "id": "pubmed-metadata-gap",
+ "project": "Clinical review supplement",
+ "targetRepository": "PubMed Central",
+ "targetType": "funder-portal",
+ "adminOwner": "compliance-desk",
+ "decision": "blocked",
+ "findings": [
+ {
+ "severity": "review",
+ "code": "mandate-deadline-near",
+ "message": "NIH deadline is within 36 hour(s)."
+ },
+ {
+ "severity": "blocker",
+ "code": "metadata-incomplete",
+ "message": "Repository export metadata is incomplete."
+ },
+ {
+ "severity": "review",
+ "code": "low-orcid-coverage",
+ "message": "ORCID coverage is 67%, below the 90% admin threshold."
+ }
+ ],
+ "escalation": "route-to-compliance-desk",
+ "auditDigest": "bf9ad08c2157beaaea1dd31169920d21829fc2303995826c52ec76d99e794c4e"
+ },
+ {
+ "id": "arxiv-ready",
+ "project": "Materials preprint export",
+ "targetRepository": "arXiv",
+ "targetType": "preprint-repository",
+ "adminOwner": "lab-admin",
+ "decision": "ready",
+ "findings": [],
+ "escalation": "release-to-sync-worker",
+ "auditDigest": "3c91a1816a086dc468584e4a6ff2c56657516e24b1f686f0616e69679d81a5ff"
+ },
+ {
+ "id": "embargoed-dataset-hold",
+ "project": "Corporate R&D catalyst screen",
+ "targetRepository": "Invenio",
+ "targetType": "institutional-repository",
+ "adminOwner": "sponsor-ops",
+ "decision": "blocked",
+ "findings": [
+ {
+ "severity": "blocker",
+ "code": "active-embargo",
+ "message": "Repository sync is blocked by an active embargo for 17 day(s)."
+ }
+ ],
+ "escalation": "route-to-sponsor-ops",
+ "auditDigest": "301fa177eba873f9fc1b77bd7521aaa2f725acccf76d8b033d8f59536aa03c22"
+ }
+ ],
+ "reportDigest": "b694760bd00672e8fd0c17f288c6007b5b4312d327f0eff8adf051b753d581a8"
+}
diff --git a/institutional-repository-sync-sla-guard/reports/repository-sync-sla-report.md b/institutional-repository-sync-sla-guard/reports/repository-sync-sla-report.md
new file mode 100644
index 00000000..4b5cb48f
--- /dev/null
+++ b/institutional-repository-sync-sla-guard/reports/repository-sync-sla-report.md
@@ -0,0 +1,58 @@
+# Institutional Repository Sync SLA Guard
+
+Generated: 2026-05-29T00:00:00.000Z
+Report digest: `b694760bd00672e8fd0c17f288c6007b5b4312d327f0eff8adf051b753d581a8`
+
+## Summary
+
+- Total jobs: 4
+- Ready: 1
+- Needs review: 0
+- Blocked: 3
+- Findings: 7
+
+## Decisions
+
+### zenodo-open-access-overdue
+
+- Decision: **blocked**
+- Project: Single-cell reproducibility atlas
+- Target: Zenodo (indexed-repository)
+- Escalation: `route-to-research-office`
+- Audit digest: `036ab26e31a2ab0b83954569ca3998b69bb6cd2bce0093b3cd974220502ac3b4`
+- Findings:
+ - blocker: mandate-deadline-missed - Horizon EU repository sync is 18 hour(s) overdue.
+ - review: sync-backlog-risk - Sync has queued for 42 hour(s) with 5 retry attempt(s).
+ - review: webhook-delivery-failed - Last institutional webhook delivery failed and requires admin visibility.
+
+### pubmed-metadata-gap
+
+- Decision: **blocked**
+- Project: Clinical review supplement
+- Target: PubMed Central (funder-portal)
+- Escalation: `route-to-compliance-desk`
+- Audit digest: `bf9ad08c2157beaaea1dd31169920d21829fc2303995826c52ec76d99e794c4e`
+- Findings:
+ - review: mandate-deadline-near - NIH deadline is within 36 hour(s).
+ - blocker: metadata-incomplete - Repository export metadata is incomplete.
+ - review: low-orcid-coverage - ORCID coverage is 67%, below the 90% admin threshold.
+
+### arxiv-ready
+
+- Decision: **ready**
+- Project: Materials preprint export
+- Target: arXiv (preprint-repository)
+- Escalation: `release-to-sync-worker`
+- Audit digest: `3c91a1816a086dc468584e4a6ff2c56657516e24b1f686f0616e69679d81a5ff`
+- Findings:
+ - info: none - No blocking or review findings.
+
+### embargoed-dataset-hold
+
+- Decision: **blocked**
+- Project: Corporate R&D catalyst screen
+- Target: Invenio (institutional-repository)
+- Escalation: `route-to-sponsor-ops`
+- Audit digest: `301fa177eba873f9fc1b77bd7521aaa2f725acccf76d8b033d8f59536aa03c22`
+- Findings:
+ - blocker: active-embargo - Repository sync is blocked by an active embargo for 17 day(s).
diff --git a/institutional-repository-sync-sla-guard/reports/summary.svg b/institutional-repository-sync-sla-guard/reports/summary.svg
new file mode 100644
index 00000000..d1bb3524
--- /dev/null
+++ b/institutional-repository-sync-sla-guard/reports/summary.svg
@@ -0,0 +1,13 @@
+
diff --git a/institutional-repository-sync-sla-guard/sample-data.js b/institutional-repository-sync-sla-guard/sample-data.js
new file mode 100644
index 00000000..fd6f9d33
--- /dev/null
+++ b/institutional-repository-sync-sla-guard/sample-data.js
@@ -0,0 +1,128 @@
+const syncJobs = [
+ {
+ id: "zenodo-open-access-overdue",
+ project: "Single-cell reproducibility atlas",
+ target: {
+ repository: "Zenodo",
+ type: "indexed-repository",
+ region: "EU",
+ },
+ mandate: {
+ funder: "Horizon EU",
+ dueInHours: -18,
+ requiresOpenAccess: true,
+ requiresDoi: true,
+ requiresOrcid: true,
+ },
+ package: {
+ metadataComplete: true,
+ doiPresent: true,
+ orcidCoveragePercent: 94,
+ license: "CC-BY-4.0",
+ embargoUntil: null,
+ artifactHashPinned: true,
+ },
+ operations: {
+ queuedHours: 42,
+ retryCount: 5,
+ lastWebhookStatus: "failed",
+ lastWebhookAgeHours: 13,
+ adminOwner: "research-office",
+ },
+ },
+ {
+ id: "pubmed-metadata-gap",
+ project: "Clinical review supplement",
+ target: {
+ repository: "PubMed Central",
+ type: "funder-portal",
+ region: "US",
+ },
+ mandate: {
+ funder: "NIH",
+ dueInHours: 36,
+ requiresOpenAccess: true,
+ requiresDoi: true,
+ requiresOrcid: true,
+ },
+ package: {
+ metadataComplete: false,
+ doiPresent: true,
+ orcidCoveragePercent: 67,
+ license: "CC-BY-4.0",
+ embargoUntil: null,
+ artifactHashPinned: true,
+ },
+ operations: {
+ queuedHours: 8,
+ retryCount: 1,
+ lastWebhookStatus: "delivered",
+ lastWebhookAgeHours: 2,
+ adminOwner: "compliance-desk",
+ },
+ },
+ {
+ id: "arxiv-ready",
+ project: "Materials preprint export",
+ target: {
+ repository: "arXiv",
+ type: "preprint-repository",
+ region: "US",
+ },
+ mandate: {
+ funder: "Internal",
+ dueInHours: 120,
+ requiresOpenAccess: false,
+ requiresDoi: false,
+ requiresOrcid: true,
+ },
+ package: {
+ metadataComplete: true,
+ doiPresent: false,
+ orcidCoveragePercent: 100,
+ license: "MIT",
+ embargoUntil: null,
+ artifactHashPinned: true,
+ },
+ operations: {
+ queuedHours: 3,
+ retryCount: 0,
+ lastWebhookStatus: "delivered",
+ lastWebhookAgeHours: 1,
+ adminOwner: "lab-admin",
+ },
+ },
+ {
+ id: "embargoed-dataset-hold",
+ project: "Corporate R&D catalyst screen",
+ target: {
+ repository: "Invenio",
+ type: "institutional-repository",
+ region: "US",
+ },
+ mandate: {
+ funder: "Industry sponsor",
+ dueInHours: 72,
+ requiresOpenAccess: false,
+ requiresDoi: true,
+ requiresOrcid: false,
+ },
+ package: {
+ metadataComplete: true,
+ doiPresent: true,
+ orcidCoveragePercent: 50,
+ license: "proprietary-review",
+ embargoUntil: "2026-06-15",
+ artifactHashPinned: true,
+ },
+ operations: {
+ queuedHours: 15,
+ retryCount: 0,
+ lastWebhookStatus: "not-sent",
+ lastWebhookAgeHours: null,
+ adminOwner: "sponsor-ops",
+ },
+ },
+];
+
+module.exports = { syncJobs };
diff --git a/institutional-repository-sync-sla-guard/test.js b/institutional-repository-sync-sla-guard/test.js
new file mode 100644
index 00000000..1e1ecf92
--- /dev/null
+++ b/institutional-repository-sync-sla-guard/test.js
@@ -0,0 +1,39 @@
+const assert = require("assert");
+
+const { embargoDays, evaluateJob, evaluateSyncJobs } = require("./index");
+const { syncJobs } = require("./sample-data");
+
+assert.strictEqual(embargoDays("2026-06-15"), 17);
+assert.strictEqual(embargoDays(null), 0);
+
+const overdue = evaluateJob(syncJobs[0]);
+assert.strictEqual(overdue.decision, "blocked");
+assert(overdue.findings.some((finding) => finding.code === "mandate-deadline-missed"));
+assert(overdue.findings.some((finding) => finding.code === "sync-backlog-risk"));
+assert(overdue.findings.some((finding) => finding.code === "webhook-delivery-failed"));
+
+const metadataGap = evaluateJob(syncJobs[1]);
+assert.strictEqual(metadataGap.decision, "blocked");
+assert(metadataGap.findings.some((finding) => finding.code === "metadata-incomplete"));
+assert(metadataGap.findings.some((finding) => finding.code === "low-orcid-coverage"));
+
+const ready = evaluateJob(syncJobs[2]);
+assert.strictEqual(ready.decision, "ready");
+assert.strictEqual(ready.escalation, "release-to-sync-worker");
+
+const embargoed = evaluateJob(syncJobs[3]);
+assert.strictEqual(embargoed.decision, "blocked");
+assert(embargoed.findings.some((finding) => finding.code === "active-embargo"));
+
+const report = evaluateSyncJobs(syncJobs);
+assert.deepStrictEqual(report.summary, {
+ total: 4,
+ ready: 1,
+ needs_review: 0,
+ blocked: 3,
+ findingCount: 7,
+});
+assert.match(report.reportDigest, /^[0-9a-f]{64}$/);
+assert(report.decisions.every((decision) => /^[0-9a-f]{64}$/.test(decision.auditDigest)));
+
+console.log("institutional-repository-sync-sla-guard tests passed");