diff --git a/enterprise-initiative-tag-governance/README.md b/enterprise-initiative-tag-governance/README.md
new file mode 100644
index 0000000..b78109f
--- /dev/null
+++ b/enterprise-initiative-tag-governance/README.md
@@ -0,0 +1,32 @@
+# Enterprise Initiative Tag Governance
+
+Self-contained Enterprise Tooling slice for SCIBASE issue #19.
+
+This module validates institution-defined initiative tags such as `GRANT-TRACKED`, `DOCTORAL-WORK`, or `PUBLIC-INITIATIVE` before they appear in admin dashboards, analytics rollups, or outbound webhook events.
+
+## What It Covers
+
+- Controlled initiative tag vocabularies for institutional admins.
+- Scope checks by department, lab, or organization.
+- Owner approval, expiry, funder linkage, required evidence, and reproducibility thresholds.
+- Private or restricted project boundary checks before dashboard publishing.
+- Mutual exclusion checks for conflicting internal and public tags.
+- Dashboard rollups by tag and department.
+- Signed webhook-ready governance events and a deterministic audit digest.
+
+## Local Validation
+
+```bash
+node enterprise-initiative-tag-governance/test.js
+node enterprise-initiative-tag-governance/demo.js
+node --check enterprise-initiative-tag-governance/index.js
+node --check enterprise-initiative-tag-governance/test.js
+node --check enterprise-initiative-tag-governance/demo.js
+ffprobe -v error -show_entries format=duration,size -show_entries stream=codec_name,width,height -of default=noprint_wrappers=1 enterprise-initiative-tag-governance/demo.mp4
+```
+
+The demo writes:
+
+- `enterprise-initiative-tag-governance/reports/reviewer-packet.json`
+- `enterprise-initiative-tag-governance/demo.svg`
+- `enterprise-initiative-tag-governance/demo.mp4` is included as the short demo artifact.
diff --git a/enterprise-initiative-tag-governance/acceptance-notes.md b/enterprise-initiative-tag-governance/acceptance-notes.md
new file mode 100644
index 0000000..f94059d
--- /dev/null
+++ b/enterprise-initiative-tag-governance/acceptance-notes.md
@@ -0,0 +1,29 @@
+# Acceptance Notes
+
+## Reviewer Checklist
+
+- Confirm `assessEnterpriseInitiativeTags` accepts synthetic policies, projects, assignments, and evidence records.
+- Confirm approved tags can appear in dashboard rollups and signed webhook events.
+- Confirm restricted or expired tags are held before dashboard publication.
+- Confirm mutually exclusive tags on the same project are blocked.
+- Confirm funder, open-access, and reproducibility requirements are surfaced as blockers or warnings.
+
+## Validation Evidence
+
+Run locally from the repository root:
+
+```bash
+node enterprise-initiative-tag-governance/test.js
+node enterprise-initiative-tag-governance/demo.js
+node --check enterprise-initiative-tag-governance/index.js
+node --check enterprise-initiative-tag-governance/test.js
+node --check enterprise-initiative-tag-governance/demo.js
+ffprobe -v error -show_entries format=duration,size -show_entries stream=codec_name,width,height -of default=noprint_wrappers=1 enterprise-initiative-tag-governance/demo.mp4
+```
+
+Expected output includes:
+
+- `enterprise-initiative-tag-governance tests passed`
+- A reviewer packet at `enterprise-initiative-tag-governance/reports/reviewer-packet.json`
+- A static demo frame at `enterprise-initiative-tag-governance/demo.svg`
+- A 1280x720 H.264 MP4 demo at `enterprise-initiative-tag-governance/demo.mp4`
diff --git a/enterprise-initiative-tag-governance/demo.js b/enterprise-initiative-tag-governance/demo.js
new file mode 100644
index 0000000..96e64cf
--- /dev/null
+++ b/enterprise-initiative-tag-governance/demo.js
@@ -0,0 +1,124 @@
+"use strict"
+
+const fs = require("node:fs")
+const path = require("node:path")
+const { assessEnterpriseInitiativeTags } = require("./index")
+
+const input = {
+ now: "2026-05-20T00:00:00.000Z",
+ organizationId: "research-office-demo",
+ policies: [
+ {
+ id: "GRANT-TRACKED",
+ label: "Grant tracked",
+ allowedScopes: ["biology", "materials"],
+ requiresOwnerApproval: true,
+ requiresExpiry: true,
+ maxDurationDays: 540,
+ requiredEvidence: ["grant-award-letter"],
+ publishToDashboard: true,
+ funderId: "nih-r01-42",
+ minimumReproducibilityScore: 80,
+ openAccessRequired: true,
+ },
+ {
+ id: "DOCTORAL-WORK",
+ label: "Doctoral work",
+ allowedScopes: ["biology", "physics"],
+ requiresOwnerApproval: true,
+ requiresExpiry: true,
+ publishToDashboard: true,
+ },
+ {
+ id: "PUBLIC-INITIATIVE",
+ label: "Public initiative",
+ allowedScopes: ["organization"],
+ requiresOwnerApproval: true,
+ requiresExpiry: true,
+ publishToDashboard: true,
+ restrictedDataAllowed: false,
+ },
+ ],
+ evidence: [{ id: "grant-award-letter" }],
+ projects: [
+ {
+ id: "project-atlas",
+ title: "Single-cell atlas release",
+ department: "biology",
+ visibility: "public",
+ dataClassification: "open",
+ funderIds: ["nih-r01-42"],
+ compliance: { openAccessStatus: "open", reproducibilityScore: 92 },
+ },
+ {
+ id: "project-embargo",
+ title: "Embargoed sponsor validation",
+ department: "oncology",
+ visibility: "private",
+ dataClassification: "restricted",
+ funderIds: ["sponsor-7"],
+ compliance: { openAccessStatus: "embargoed", reproducibilityScore: 68 },
+ },
+ ],
+ assignments: [
+ {
+ projectId: "project-atlas",
+ tagId: "GRANT-TRACKED",
+ scope: "biology",
+ assignedAt: "2026-01-10T00:00:00.000Z",
+ expiresAt: "2026-12-31T00:00:00.000Z",
+ ownerApproval: true,
+ evidenceIds: ["grant-award-letter"],
+ },
+ {
+ projectId: "project-atlas",
+ tagId: "DOCTORAL-WORK",
+ scope: "biology",
+ assignedAt: "2026-03-01T00:00:00.000Z",
+ expiresAt: "2026-11-30T00:00:00.000Z",
+ ownerApproval: true,
+ },
+ {
+ projectId: "project-embargo",
+ tagId: "PUBLIC-INITIATIVE",
+ scope: "organization",
+ assignedAt: "2025-01-01T00:00:00.000Z",
+ expiresAt: "2026-01-01T00:00:00.000Z",
+ ownerApproval: false,
+ },
+ ],
+}
+
+const result = assessEnterpriseInitiativeTags(input)
+const outDir = path.join(__dirname, "reports")
+fs.mkdirSync(outDir, { recursive: true })
+fs.writeFileSync(path.join(outDir, "reviewer-packet.json"), `${JSON.stringify(result, null, 2)}\n`)
+
+const svg = `
+
+`
+
+fs.writeFileSync(path.join(__dirname, "demo.svg"), svg)
+console.log(`status=${result.status}`)
+console.log(`approved=${result.summary.approved} held=${result.summary.held} warnings=${result.summary.warningCount}`)
+console.log(`auditDigest=${result.auditDigest}`)
+console.log(`wrote ${path.relative(process.cwd(), path.join(outDir, "reviewer-packet.json"))}`)
+console.log(`wrote ${path.relative(process.cwd(), path.join(__dirname, "demo.svg"))}`)
diff --git a/enterprise-initiative-tag-governance/demo.mp4 b/enterprise-initiative-tag-governance/demo.mp4
new file mode 100644
index 0000000..6e30a81
Binary files /dev/null and b/enterprise-initiative-tag-governance/demo.mp4 differ
diff --git a/enterprise-initiative-tag-governance/demo.svg b/enterprise-initiative-tag-governance/demo.svg
new file mode 100644
index 0000000..2e0b12a
--- /dev/null
+++ b/enterprise-initiative-tag-governance/demo.svg
@@ -0,0 +1,20 @@
+
+
diff --git a/enterprise-initiative-tag-governance/index.js b/enterprise-initiative-tag-governance/index.js
new file mode 100644
index 0000000..0d8b520
--- /dev/null
+++ b/enterprise-initiative-tag-governance/index.js
@@ -0,0 +1,319 @@
+"use strict"
+
+const crypto = require("node:crypto")
+
+function stableStringify(value) {
+ if (Array.isArray(value)) return `[${value.map(stableStringify).join(",")}]`
+ if (value && typeof value === "object") {
+ return `{${Object.keys(value)
+ .sort()
+ .map((key) => `${JSON.stringify(key)}:${stableStringify(value[key])}`)
+ .join(",")}}`
+ }
+ return JSON.stringify(value)
+}
+
+function digest(value) {
+ return crypto.createHash("sha256").update(stableStringify(value)).digest("hex")
+}
+
+function normalizeId(value) {
+ return String(value || "")
+ .trim()
+ .toLowerCase()
+ .replace(/[^a-z0-9]+/g, "-")
+ .replace(/^-+|-+$/g, "")
+}
+
+function parseDate(value) {
+ if (!value) return null
+ const parsed = new Date(value)
+ return Number.isNaN(parsed.getTime()) ? null : parsed
+}
+
+function daysBetween(start, end) {
+ return Math.ceil((end.getTime() - start.getTime()) / (24 * 60 * 60 * 1000))
+}
+
+function toArray(value) {
+ return Array.isArray(value) ? value : []
+}
+
+function makePolicyMap(policies) {
+ return new Map(
+ toArray(policies).map((policy) => {
+ const id = normalizeId(policy.id || policy.label)
+ return [
+ id,
+ {
+ ...policy,
+ id,
+ label: policy.label || id,
+ allowedScopes: toArray(policy.allowedScopes).map(normalizeId),
+ mutuallyExclusiveWith: toArray(policy.mutuallyExclusiveWith).map(normalizeId),
+ requiredEvidence: toArray(policy.requiredEvidence),
+ },
+ ]
+ }),
+ )
+}
+
+function makeProjectMap(projects) {
+ return new Map(
+ toArray(projects).map((project) => [
+ String(project.id),
+ {
+ ...project,
+ funderIds: toArray(project.funderIds).map(String),
+ tags: toArray(project.tags),
+ },
+ ]),
+ )
+}
+
+function expandAssignments(input) {
+ const explicit = toArray(input.assignments)
+ const projectTags = toArray(input.projects).flatMap((project) =>
+ toArray(project.initiativeTags).map((tag) => ({
+ ...tag,
+ projectId: project.id,
+ })),
+ )
+
+ return [...explicit, ...projectTags].map((assignment, index) => ({
+ ...assignment,
+ index,
+ projectId: String(assignment.projectId || ""),
+ tagId: normalizeId(assignment.tagId || assignment.tag || assignment.label),
+ scope: normalizeId(assignment.scope || assignment.department || assignment.lab || "organization"),
+ evidenceIds: toArray(assignment.evidenceIds).map(String),
+ }))
+}
+
+function scopeMatches(policy, project, assignment) {
+ if (!policy.allowedScopes.length || policy.allowedScopes.includes("*")) return true
+ const candidates = [
+ assignment.scope,
+ normalizeId(project.department),
+ normalizeId(project.lab),
+ normalizeId(project.organizationId),
+ "organization",
+ ].filter(Boolean)
+
+ return candidates.some((candidate) => policy.allowedScopes.includes(candidate))
+}
+
+function hasRestrictedBoundary(project) {
+ const visibility = normalizeId(project.visibility)
+ const dataClass = normalizeId(project.dataClassification)
+ return ["private", "restricted", "sensitive", "embargoed"].includes(visibility) ||
+ ["private", "restricted", "sensitive", "regulated", "phi"].includes(dataClass)
+}
+
+function findProjectConflicts(assignment, siblings, policy, policyById) {
+ const conflicts = []
+ for (const sibling of siblings) {
+ if (sibling.index === assignment.index || sibling.tagId === assignment.tagId) continue
+ const siblingPolicy = policyById.get(sibling.tagId)
+ if (!siblingPolicy) continue
+ if (policy.mutuallyExclusiveWith.includes(sibling.tagId)) conflicts.push(sibling.tagId)
+ if (siblingPolicy.mutuallyExclusiveWith.includes(assignment.tagId)) conflicts.push(sibling.tagId)
+ }
+ return [...new Set(conflicts)]
+}
+
+function evaluateAssignment(assignment, context) {
+ const { evidenceIds, now, policyById, projectById, assignmentsByProject } = context
+ const blockers = []
+ const warnings = []
+ const project = projectById.get(assignment.projectId)
+ const policy = policyById.get(assignment.tagId)
+
+ if (!assignment.projectId) blockers.push("missing project id")
+ if (!assignment.tagId) blockers.push("missing initiative tag id")
+ if (!project) blockers.push(`unknown project ${assignment.projectId || "(blank)"}`)
+ if (!policy) blockers.push(`unknown initiative tag ${assignment.tagId || "(blank)"}`)
+
+ if (project && policy) {
+ if (!scopeMatches(policy, project, assignment)) {
+ blockers.push(`tag ${policy.id} is not allowed for scope ${assignment.scope}`)
+ }
+
+ if (policy.requiresOwnerApproval && !assignment.ownerApproval) {
+ blockers.push(`tag ${policy.id} is missing owner approval`)
+ }
+
+ const missingEvidence = policy.requiredEvidence.filter((requiredId) => {
+ return !assignment.evidenceIds.includes(requiredId) && !evidenceIds.has(requiredId)
+ })
+ if (missingEvidence.length > 0) {
+ blockers.push(`tag ${policy.id} is missing required evidence: ${missingEvidence.join(", ")}`)
+ }
+
+ const assignedAt = parseDate(assignment.assignedAt)
+ const expiresAt = parseDate(assignment.expiresAt)
+ if (policy.requiresExpiry && !expiresAt) {
+ blockers.push(`tag ${policy.id} is missing an expiry date`)
+ }
+ if (expiresAt && expiresAt < now) {
+ blockers.push(`tag ${policy.id} expired on ${expiresAt.toISOString().slice(0, 10)}`)
+ }
+ if (assignedAt && expiresAt && policy.maxDurationDays && daysBetween(assignedAt, expiresAt) > policy.maxDurationDays) {
+ warnings.push(`tag ${policy.id} exceeds the ${policy.maxDurationDays} day maximum duration`)
+ }
+
+ if (policy.publishToDashboard && hasRestrictedBoundary(project) && !policy.restrictedDataAllowed) {
+ blockers.push(`tag ${policy.id} would expose a restricted project in admin dashboard rollups`)
+ }
+
+ if (policy.funderId && !project.funderIds.includes(String(policy.funderId))) {
+ blockers.push(`tag ${policy.id} requires funder ${policy.funderId}`)
+ }
+
+ const score = Number(project.compliance?.reproducibilityScore ?? 0)
+ if (policy.minimumReproducibilityScore && score < policy.minimumReproducibilityScore) {
+ warnings.push(`project reproducibility score ${score} is below ${policy.minimumReproducibilityScore}`)
+ }
+
+ if (policy.openAccessRequired && normalizeId(project.compliance?.openAccessStatus) !== "open") {
+ warnings.push(`project open access status is ${project.compliance?.openAccessStatus || "missing"}`)
+ }
+
+ const conflicts = findProjectConflicts(
+ assignment,
+ assignmentsByProject.get(assignment.projectId) || [],
+ policy,
+ policyById,
+ )
+ if (conflicts.length > 0) {
+ blockers.push(`tag ${policy.id} conflicts with ${conflicts.join(", ")}`)
+ }
+ }
+
+ const status = blockers.length > 0 ? "held" : warnings.length > 0 ? "needs-review" : "approved"
+ const event = {
+ type: status === "approved" ? "enterprise_tag.approved" : "enterprise_tag.held",
+ projectId: assignment.projectId,
+ tagId: assignment.tagId,
+ status,
+ blockerCount: blockers.length,
+ warningCount: warnings.length,
+ issuedAt: now.toISOString(),
+ }
+
+ return {
+ projectId: assignment.projectId,
+ tagId: assignment.tagId,
+ label: policy?.label || assignment.tagId,
+ scope: assignment.scope,
+ status,
+ blockers,
+ warnings,
+ dashboardVisible: Boolean(policy?.publishToDashboard && status !== "held"),
+ event: {
+ ...event,
+ signature: digest(event),
+ },
+ }
+}
+
+function buildDashboardRollups(assignmentReports, projectById) {
+ const tagRollups = new Map()
+ const departmentRollups = new Map()
+
+ for (const report of assignmentReports) {
+ const project = projectById.get(report.projectId)
+ const department = project?.department || "unassigned"
+ if (!tagRollups.has(report.tagId)) {
+ tagRollups.set(report.tagId, {
+ tagId: report.tagId,
+ label: report.label,
+ projectCount: 0,
+ approvedCount: 0,
+ heldCount: 0,
+ needsReviewCount: 0,
+ warningCount: 0,
+ blockerCount: 0,
+ departments: new Set(),
+ })
+ }
+ const tag = tagRollups.get(report.tagId)
+ tag.projectCount += 1
+ tag.warningCount += report.warnings.length
+ tag.blockerCount += report.blockers.length
+ tag.departments.add(department)
+ if (report.status === "approved") tag.approvedCount += 1
+ if (report.status === "held") tag.heldCount += 1
+ if (report.status === "needs-review") tag.needsReviewCount += 1
+
+ if (!departmentRollups.has(department)) {
+ departmentRollups.set(department, {
+ department,
+ assignedTags: 0,
+ dashboardVisibleTags: 0,
+ heldTags: 0,
+ })
+ }
+ const dept = departmentRollups.get(department)
+ dept.assignedTags += 1
+ if (report.dashboardVisible) dept.dashboardVisibleTags += 1
+ if (report.status === "held") dept.heldTags += 1
+ }
+
+ return {
+ byTag: [...tagRollups.values()]
+ .map((rollup) => ({ ...rollup, departments: [...rollup.departments].sort() }))
+ .sort((a, b) => a.tagId.localeCompare(b.tagId)),
+ byDepartment: [...departmentRollups.values()].sort((a, b) => a.department.localeCompare(b.department)),
+ }
+}
+
+function assessEnterpriseInitiativeTags(input = {}) {
+ const now = parseDate(input.now) || new Date()
+ const policyById = makePolicyMap(input.policies)
+ const projectById = makeProjectMap(input.projects)
+ const evidenceIds = new Set(toArray(input.evidence).map((item) => String(item.id)))
+ const assignments = expandAssignments(input)
+ const assignmentsByProject = new Map()
+
+ for (const assignment of assignments) {
+ if (!assignmentsByProject.has(assignment.projectId)) assignmentsByProject.set(assignment.projectId, [])
+ assignmentsByProject.get(assignment.projectId).push(assignment)
+ }
+
+ const context = { evidenceIds, now, policyById, projectById, assignmentsByProject }
+ const assignmentReports = assignments.map((assignment) => evaluateAssignment(assignment, context))
+ const blockerCount = assignmentReports.reduce((sum, report) => sum + report.blockers.length, 0)
+ const warningCount = assignmentReports.reduce((sum, report) => sum + report.warnings.length, 0)
+ const dashboardRollups = buildDashboardRollups(assignmentReports, projectById)
+
+ const reviewerPacket = {
+ organizationId: input.organizationId || "enterprise-org",
+ evaluatedAt: now.toISOString(),
+ status: blockerCount > 0 ? "blocked" : warningCount > 0 ? "needs-review" : "ready",
+ summary: {
+ policies: policyById.size,
+ projects: projectById.size,
+ assignments: assignments.length,
+ approved: assignmentReports.filter((report) => report.status === "approved").length,
+ needsReview: assignmentReports.filter((report) => report.status === "needs-review").length,
+ held: assignmentReports.filter((report) => report.status === "held").length,
+ blockerCount,
+ warningCount,
+ },
+ assignmentReports,
+ dashboardRollups,
+ webhookEvents: assignmentReports.map((report) => report.event),
+ }
+
+ return {
+ ...reviewerPacket,
+ auditDigest: digest(reviewerPacket),
+ }
+}
+
+module.exports = {
+ assessEnterpriseInitiativeTags,
+ normalizeId,
+ stableStringify,
+}
diff --git a/enterprise-initiative-tag-governance/reports/reviewer-packet.json b/enterprise-initiative-tag-governance/reports/reviewer-packet.json
new file mode 100644
index 0000000..a42ef42
--- /dev/null
+++ b/enterprise-initiative-tag-governance/reports/reviewer-packet.json
@@ -0,0 +1,171 @@
+{
+ "organizationId": "research-office-demo",
+ "evaluatedAt": "2026-05-20T00:00:00.000Z",
+ "status": "blocked",
+ "summary": {
+ "policies": 3,
+ "projects": 2,
+ "assignments": 3,
+ "approved": 2,
+ "needsReview": 0,
+ "held": 1,
+ "blockerCount": 3,
+ "warningCount": 0
+ },
+ "assignmentReports": [
+ {
+ "projectId": "project-atlas",
+ "tagId": "grant-tracked",
+ "label": "Grant tracked",
+ "scope": "biology",
+ "status": "approved",
+ "blockers": [],
+ "warnings": [],
+ "dashboardVisible": true,
+ "event": {
+ "type": "enterprise_tag.approved",
+ "projectId": "project-atlas",
+ "tagId": "grant-tracked",
+ "status": "approved",
+ "blockerCount": 0,
+ "warningCount": 0,
+ "issuedAt": "2026-05-20T00:00:00.000Z",
+ "signature": "0cd50d892226890059543852ffb28317a085118d9f2d8ba7ce29bc45d870db6e"
+ }
+ },
+ {
+ "projectId": "project-atlas",
+ "tagId": "doctoral-work",
+ "label": "Doctoral work",
+ "scope": "biology",
+ "status": "approved",
+ "blockers": [],
+ "warnings": [],
+ "dashboardVisible": true,
+ "event": {
+ "type": "enterprise_tag.approved",
+ "projectId": "project-atlas",
+ "tagId": "doctoral-work",
+ "status": "approved",
+ "blockerCount": 0,
+ "warningCount": 0,
+ "issuedAt": "2026-05-20T00:00:00.000Z",
+ "signature": "67cf892ece6bccbfa50bc3e7f67e8d5e47923b8a7a9db135a5ffab7dbf768c37"
+ }
+ },
+ {
+ "projectId": "project-embargo",
+ "tagId": "public-initiative",
+ "label": "Public initiative",
+ "scope": "organization",
+ "status": "held",
+ "blockers": [
+ "tag public-initiative is missing owner approval",
+ "tag public-initiative expired on 2026-01-01",
+ "tag public-initiative would expose a restricted project in admin dashboard rollups"
+ ],
+ "warnings": [],
+ "dashboardVisible": false,
+ "event": {
+ "type": "enterprise_tag.held",
+ "projectId": "project-embargo",
+ "tagId": "public-initiative",
+ "status": "held",
+ "blockerCount": 3,
+ "warningCount": 0,
+ "issuedAt": "2026-05-20T00:00:00.000Z",
+ "signature": "ad4a314474888e8721c6931dc65e95819ab3c5044df033aa6879d31159dc29a5"
+ }
+ }
+ ],
+ "dashboardRollups": {
+ "byTag": [
+ {
+ "tagId": "doctoral-work",
+ "label": "Doctoral work",
+ "projectCount": 1,
+ "approvedCount": 1,
+ "heldCount": 0,
+ "needsReviewCount": 0,
+ "warningCount": 0,
+ "blockerCount": 0,
+ "departments": [
+ "biology"
+ ]
+ },
+ {
+ "tagId": "grant-tracked",
+ "label": "Grant tracked",
+ "projectCount": 1,
+ "approvedCount": 1,
+ "heldCount": 0,
+ "needsReviewCount": 0,
+ "warningCount": 0,
+ "blockerCount": 0,
+ "departments": [
+ "biology"
+ ]
+ },
+ {
+ "tagId": "public-initiative",
+ "label": "Public initiative",
+ "projectCount": 1,
+ "approvedCount": 0,
+ "heldCount": 1,
+ "needsReviewCount": 0,
+ "warningCount": 0,
+ "blockerCount": 3,
+ "departments": [
+ "oncology"
+ ]
+ }
+ ],
+ "byDepartment": [
+ {
+ "department": "biology",
+ "assignedTags": 2,
+ "dashboardVisibleTags": 2,
+ "heldTags": 0
+ },
+ {
+ "department": "oncology",
+ "assignedTags": 1,
+ "dashboardVisibleTags": 0,
+ "heldTags": 1
+ }
+ ]
+ },
+ "webhookEvents": [
+ {
+ "type": "enterprise_tag.approved",
+ "projectId": "project-atlas",
+ "tagId": "grant-tracked",
+ "status": "approved",
+ "blockerCount": 0,
+ "warningCount": 0,
+ "issuedAt": "2026-05-20T00:00:00.000Z",
+ "signature": "0cd50d892226890059543852ffb28317a085118d9f2d8ba7ce29bc45d870db6e"
+ },
+ {
+ "type": "enterprise_tag.approved",
+ "projectId": "project-atlas",
+ "tagId": "doctoral-work",
+ "status": "approved",
+ "blockerCount": 0,
+ "warningCount": 0,
+ "issuedAt": "2026-05-20T00:00:00.000Z",
+ "signature": "67cf892ece6bccbfa50bc3e7f67e8d5e47923b8a7a9db135a5ffab7dbf768c37"
+ },
+ {
+ "type": "enterprise_tag.held",
+ "projectId": "project-embargo",
+ "tagId": "public-initiative",
+ "status": "held",
+ "blockerCount": 3,
+ "warningCount": 0,
+ "issuedAt": "2026-05-20T00:00:00.000Z",
+ "signature": "ad4a314474888e8721c6931dc65e95819ab3c5044df033aa6879d31159dc29a5"
+ }
+ ],
+ "auditDigest": "ce54335865ff8107bef5be0524ae7bab67d38b5d234f3530b518622759f1e701"
+}
diff --git a/enterprise-initiative-tag-governance/requirements-map.md b/enterprise-initiative-tag-governance/requirements-map.md
new file mode 100644
index 0000000..a4bd1bf
--- /dev/null
+++ b/enterprise-initiative-tag-governance/requirements-map.md
@@ -0,0 +1,14 @@
+# Requirements Map
+
+| Issue #19 requirement | Coverage in this module |
+| --- | --- |
+| Admin dashboards | Produces dashboard rollups by initiative tag and department, with approved, held, warning, and blocker counts. |
+| Custom tags or flags for internal initiatives | Validates controlled tag policies such as `GRANT-TRACKED`, `DOCTORAL-WORK`, and `PUBLIC-INITIATIVE`. |
+| Contributor and productivity analytics integrity | Holds tags that lack owner approval, required evidence, valid scope, or expiry before they can influence rollups. |
+| Compliance tracking | Checks funder linkage, open-access status, reproducibility score thresholds, and restricted-data dashboard exposure. |
+| API and webhooks | Emits signed webhook-ready governance events for each approved or held initiative tag. |
+| Enterprise-scale oversight | Produces deterministic reviewer packets and audit digests suitable for institutional admin review. |
+
+## Non-Overlap Note
+
+This submission is distinct from the existing dashboard/export/webhook/compliance/identity/retention/data-residency/SLA/lab-inventory/secret-rotation/quota/API-change/connector-certification/incident/funder-reporting/AI-model-governance/dashboard-attribution submissions. It focuses specifically on institution-defined initiative tag governance before dashboard and webhook publication.
diff --git a/enterprise-initiative-tag-governance/test.js b/enterprise-initiative-tag-governance/test.js
new file mode 100644
index 0000000..f0f2953
--- /dev/null
+++ b/enterprise-initiative-tag-governance/test.js
@@ -0,0 +1,181 @@
+"use strict"
+
+const assert = require("node:assert/strict")
+const { assessEnterpriseInitiativeTags, normalizeId } = require("./index")
+
+const now = "2026-05-20T00:00:00.000Z"
+
+{
+ const result = assessEnterpriseInitiativeTags({
+ now,
+ organizationId: "university-alpha",
+ policies: [
+ {
+ id: "GRANT-TRACKED",
+ label: "Grant tracked",
+ allowedScopes: ["biology", "chemistry"],
+ requiresOwnerApproval: true,
+ requiresExpiry: true,
+ maxDurationDays: 540,
+ requiredEvidence: ["grant-award-letter"],
+ publishToDashboard: true,
+ funderId: "nih-r01-42",
+ minimumReproducibilityScore: 80,
+ openAccessRequired: true,
+ },
+ {
+ id: "DOCTORAL-WORK",
+ label: "Doctoral work",
+ allowedScopes: ["biology"],
+ requiresOwnerApproval: true,
+ requiresExpiry: true,
+ publishToDashboard: true,
+ },
+ ],
+ evidence: [{ id: "grant-award-letter" }],
+ projects: [
+ {
+ id: "project-1",
+ title: "Single-cell atlas release",
+ department: "biology",
+ visibility: "public",
+ dataClassification: "open",
+ funderIds: ["nih-r01-42"],
+ compliance: { openAccessStatus: "open", reproducibilityScore: 92 },
+ },
+ ],
+ assignments: [
+ {
+ projectId: "project-1",
+ tagId: "GRANT-TRACKED",
+ scope: "biology",
+ assignedAt: "2026-01-10T00:00:00.000Z",
+ expiresAt: "2026-12-31T00:00:00.000Z",
+ ownerApproval: true,
+ evidenceIds: ["grant-award-letter"],
+ },
+ {
+ projectId: "project-1",
+ tagId: "DOCTORAL-WORK",
+ scope: "biology",
+ assignedAt: "2026-03-01T00:00:00.000Z",
+ expiresAt: "2026-11-30T00:00:00.000Z",
+ ownerApproval: true,
+ },
+ ],
+ })
+
+ assert.equal(result.status, "ready")
+ assert.equal(result.summary.approved, 2)
+ assert.equal(result.dashboardRollups.byTag.find((rollup) => rollup.tagId === "grant-tracked").projectCount, 1)
+ assert.match(result.webhookEvents[0].signature, /^[0-9a-f]{64}$/)
+ assert.match(result.auditDigest, /^[0-9a-f]{64}$/)
+}
+
+{
+ const result = assessEnterpriseInitiativeTags({
+ now,
+ policies: [
+ {
+ id: "PUBLIC-INITIATIVE",
+ label: "Public initiative",
+ allowedScopes: ["organization"],
+ requiresOwnerApproval: true,
+ requiresExpiry: true,
+ publishToDashboard: true,
+ restrictedDataAllowed: false,
+ },
+ ],
+ projects: [
+ {
+ id: "project-private",
+ title: "Embargoed sponsor project",
+ department: "oncology",
+ visibility: "private",
+ dataClassification: "restricted",
+ compliance: { openAccessStatus: "embargoed", reproducibilityScore: 70 },
+ },
+ ],
+ assignments: [
+ {
+ projectId: "project-private",
+ tagId: "PUBLIC-INITIATIVE",
+ scope: "organization",
+ assignedAt: "2025-01-01T00:00:00.000Z",
+ expiresAt: "2026-01-01T00:00:00.000Z",
+ ownerApproval: false,
+ },
+ ],
+ })
+
+ const report = result.assignmentReports[0]
+ assert.equal(result.status, "blocked")
+ assert.equal(report.status, "held")
+ assert.ok(report.blockers.includes("tag public-initiative is missing owner approval"))
+ assert.ok(report.blockers.includes("tag public-initiative expired on 2026-01-01"))
+ assert.ok(report.blockers.includes("tag public-initiative would expose a restricted project in admin dashboard rollups"))
+}
+
+{
+ const result = assessEnterpriseInitiativeTags({
+ now,
+ policies: [
+ {
+ id: "PUBLIC-RELEASE",
+ allowedScopes: ["physics"],
+ mutuallyExclusiveWith: ["INTERNAL-ONLY"],
+ publishToDashboard: true,
+ },
+ {
+ id: "INTERNAL-ONLY",
+ allowedScopes: ["physics"],
+ mutuallyExclusiveWith: ["PUBLIC-RELEASE"],
+ publishToDashboard: false,
+ },
+ ],
+ projects: [{ id: "project-2", department: "physics", visibility: "public", dataClassification: "open" }],
+ assignments: [
+ { projectId: "project-2", tagId: "PUBLIC-RELEASE", scope: "physics" },
+ { projectId: "project-2", tagId: "INTERNAL-ONLY", scope: "physics" },
+ ],
+ })
+
+ assert.equal(result.status, "blocked")
+ assert.ok(result.assignmentReports[0].blockers.includes("tag public-release conflicts with internal-only"))
+ assert.ok(result.assignmentReports[1].blockers.includes("tag internal-only conflicts with public-release"))
+}
+
+{
+ const result = assessEnterpriseInitiativeTags({
+ now,
+ policies: [
+ {
+ id: "Funder Export",
+ allowedScopes: ["chemistry"],
+ funderId: "horizon-eu-9",
+ minimumReproducibilityScore: 90,
+ openAccessRequired: true,
+ publishToDashboard: true,
+ },
+ ],
+ projects: [
+ {
+ id: "project-3",
+ department: "chemistry",
+ visibility: "public",
+ dataClassification: "open",
+ funderIds: ["ukri-22"],
+ compliance: { openAccessStatus: "closed", reproducibilityScore: 82 },
+ },
+ ],
+ assignments: [{ projectId: "project-3", tagId: "Funder Export", scope: "chemistry" }],
+ })
+
+ assert.equal(normalizeId("Funder Export"), "funder-export")
+ assert.equal(result.status, "blocked")
+ assert.ok(result.assignmentReports[0].blockers.includes("tag funder-export requires funder horizon-eu-9"))
+ assert.ok(result.assignmentReports[0].warnings.includes("project reproducibility score 82 is below 90"))
+ assert.ok(result.assignmentReports[0].warnings.includes("project open access status is closed"))
+}
+
+console.log("enterprise-initiative-tag-governance tests passed")