diff --git a/enterprise-usage-cost-allocation-guard/README.md b/enterprise-usage-cost-allocation-guard/README.md
new file mode 100644
index 00000000..9beecc05
--- /dev/null
+++ b/enterprise-usage-cost-allocation-guard/README.md
@@ -0,0 +1,34 @@
+# Enterprise Usage Cost Allocation Guard
+
+This module provides a focused Enterprise Tooling slice for SCIBASE issue #19.
+It reconciles synthetic storage, compute, submission, and AI-review usage against
+lab cost centers, grants, owner sponsorship, funding windows, and private-project
+visibility rules before those costs are published to institutional dashboards or
+grant chargeback exports.
+
+## What It Covers
+
+- Enterprise admin dashboard usage allocation readiness.
+- Grant and cost-center chargeback validation.
+- Hold decisions for missing evidence, unknown ownership, and out-of-window spend.
+- Reallocation decisions for disallowed cost centers or usage types.
+- Private-project review decisions that suppress sensitive project titles.
+- Deterministic audit digests and reviewer packets.
+
+## Run
+
+```bash
+npm run check
+npm test
+npm run demo
+```
+
+Generated artifacts are written to `reports/`:
+
+- `summary.json`
+- `reviewer-packet.md`
+- `summary.svg`
+- `demo.mp4`
+
+The data is synthetic and does not contact live billing, grant, identity, or
+provider systems.
diff --git a/enterprise-usage-cost-allocation-guard/acceptance-notes.md b/enterprise-usage-cost-allocation-guard/acceptance-notes.md
new file mode 100644
index 00000000..3dd7dc60
--- /dev/null
+++ b/enterprise-usage-cost-allocation-guard/acceptance-notes.md
@@ -0,0 +1,21 @@
+# Acceptance Notes
+
+The guard accepts synthetic enterprise usage records and classifies each record
+as one of:
+
+- `approved`: ready for dashboard and grant chargeback export.
+- `hold`: missing required evidence, ownership, amount, or funding-window validity.
+- `reallocate`: valid usage that must move to a permitted cost center.
+- `private_review`: valid private/restricted project usage that requires
+ aggregate-only admin handling before publication.
+
+Validation commands:
+
+```bash
+npm run check
+npm test
+npm run demo
+ffprobe -v error -show_entries format=duration,size -show_entries stream=codec_name,width,height,pix_fmt -of default=noprint_wrappers=1 reports/demo.mp4
+git diff --check
+git diff --cached --check
+```
diff --git a/enterprise-usage-cost-allocation-guard/demo.js b/enterprise-usage-cost-allocation-guard/demo.js
new file mode 100644
index 00000000..0890d18c
--- /dev/null
+++ b/enterprise-usage-cost-allocation-guard/demo.js
@@ -0,0 +1,31 @@
+const fs = require("node:fs");
+const path = require("node:path");
+const {
+ analyzeEnterpriseUsage,
+ renderMarkdownReport,
+ renderSvgSummary,
+} = require("./index");
+const { sampleEnterpriseUsagePacket } = require("./sample-data");
+
+const reportsDir = path.join(__dirname, "reports");
+fs.mkdirSync(reportsDir, { recursive: true });
+
+const result = analyzeEnterpriseUsage(sampleEnterpriseUsagePacket, {
+ asOf: "2026-05-22T12:00:00.000Z",
+});
+
+fs.writeFileSync(
+ path.join(reportsDir, "summary.json"),
+ `${JSON.stringify(result, null, 2)}\n`
+);
+fs.writeFileSync(
+ path.join(reportsDir, "reviewer-packet.md"),
+ renderMarkdownReport(result)
+);
+fs.writeFileSync(
+ path.join(reportsDir, "summary.svg"),
+ renderSvgSummary(result)
+);
+
+console.log("enterprise usage cost allocation guard demo artifacts written");
+console.log(`audit digest: ${result.auditDigest}`);
diff --git a/enterprise-usage-cost-allocation-guard/index.js b/enterprise-usage-cost-allocation-guard/index.js
new file mode 100644
index 00000000..86101424
--- /dev/null
+++ b/enterprise-usage-cost-allocation-guard/index.js
@@ -0,0 +1,344 @@
+const crypto = require("node:crypto");
+
+const APPROVED = "approved";
+const HOLD = "hold";
+const REALLOCATE = "reallocate";
+const PRIVATE_REVIEW = "private_review";
+
+function parseDate(value, label) {
+ if (!value) return null;
+ const date = new Date(value);
+ if (Number.isNaN(date.getTime())) {
+ throw new Error(`invalid date for ${label}`);
+ }
+ return date;
+}
+
+function requireFields(value, fields, label) {
+ for (const field of fields) {
+ if (value[field] === undefined || value[field] === null || value[field] === "") {
+ throw new Error(`missing required ${label} field: ${field}`);
+ }
+ }
+}
+
+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 createAuditDigest(result) {
+ const payload = {
+ asOf: result.asOf,
+ totals: result.totals,
+ decisions: result.decisions.map((decision) => ({
+ id: decision.id,
+ status: decision.status,
+ labId: decision.labId,
+ grantId: decision.grantId,
+ amountUsd: decision.amountUsd,
+ reasons: decision.reasons,
+ })),
+ };
+
+ return crypto.createHash("sha256").update(stableStringify(payload)).digest("hex");
+}
+
+function buildLookup(items, key) {
+ const lookup = new Map();
+ for (const item of items || []) {
+ requireFields(item, [key], key);
+ lookup.set(item[key], item);
+ }
+ return lookup;
+}
+
+function withinWindow(date, start, end) {
+ if (start && date < start) return false;
+ if (end && date > end) return false;
+ return true;
+}
+
+function evaluateUsage(record, context) {
+ requireFields(
+ record,
+ [
+ "id",
+ "labId",
+ "projectId",
+ "projectVisibility",
+ "usageType",
+ "amountUsd",
+ "costCenterId",
+ "grantId",
+ "ownerId",
+ "occurredAt",
+ ],
+ "usage"
+ );
+
+ const lab = context.labs.get(record.labId);
+ const grant = context.grants.get(record.grantId);
+ const occurredAt = parseDate(record.occurredAt, `occurredAt for ${record.id}`);
+ const reasons = [];
+ const actions = [];
+ const base = {
+ id: record.id,
+ labId: record.labId,
+ projectId: record.projectId,
+ projectVisibility: record.projectVisibility,
+ usageType: record.usageType,
+ amountUsd: Number(record.amountUsd),
+ costCenterId: record.costCenterId,
+ grantId: record.grantId,
+ ownerId: record.ownerId,
+ approvedAmountUsd: 0,
+ heldAmountUsd: 0,
+ reallocatedAmountUsd: 0,
+ reasons,
+ actions,
+ };
+
+ if (!lab) reasons.push("lab is not registered for enterprise allocation");
+ if (!grant) reasons.push("grant is not registered for chargeback");
+ if (!Array.isArray(record.evidenceIds) || record.evidenceIds.length === 0) {
+ reasons.push("usage record has no immutable job/export evidence");
+ }
+
+ if (!Number.isFinite(base.amountUsd) || base.amountUsd <= 0) {
+ reasons.push("amount must be a positive number");
+ }
+
+ if (reasons.length > 0) {
+ return {
+ ...base,
+ status: HOLD,
+ heldAmountUsd: base.amountUsd,
+ actions: [
+ "Hold chargeback from dashboard totals",
+ "Request missing lab, grant, amount, or evidence mapping",
+ ],
+ };
+ }
+
+ const grantStart = parseDate(grant.startsAt, `grant ${grant.grantId} startsAt`);
+ const grantEnd = parseDate(grant.endsAt, `grant ${grant.grantId} endsAt`);
+ const labAllowsCostCenter = lab.activeCostCenters.includes(record.costCenterId);
+ const grantAllowsCostCenter = grant.allowedCostCenters.includes(record.costCenterId);
+ const grantAllowsUsage = grant.allowedUsageTypes.includes(record.usageType);
+ const ownerAllowed = lab.ownerIds.includes(record.ownerId);
+
+ if (!withinWindow(occurredAt, grantStart, grantEnd)) {
+ return {
+ ...base,
+ status: HOLD,
+ heldAmountUsd: base.amountUsd,
+ reasons: ["usage occurred outside the grant funding window"],
+ actions: [
+ "Exclude from automated grant chargeback",
+ "Route to enterprise finance review",
+ ],
+ };
+ }
+
+ if (!ownerAllowed) {
+ return {
+ ...base,
+ status: HOLD,
+ heldAmountUsd: base.amountUsd,
+ reasons: ["usage owner is not authorized for the lab cost center"],
+ actions: [
+ "Hold chargeback",
+ "Ask lab admin to confirm owner sponsorship",
+ ],
+ };
+ }
+
+ if (!labAllowsCostCenter || !grantAllowsCostCenter || !grantAllowsUsage) {
+ return {
+ ...base,
+ status: REALLOCATE,
+ reallocatedAmountUsd: base.amountUsd,
+ reasons: [
+ !labAllowsCostCenter
+ ? "cost center is not active for this lab"
+ : "grant does not permit this cost center or usage type",
+ ],
+ actions: [
+ "Remove from grant spend until reallocated",
+ "Suggest a permitted lab cost center before dashboard publication",
+ ],
+ };
+ }
+
+ if (record.projectVisibility === "private" || record.privateDataClass) {
+ return {
+ ...base,
+ status: PRIVATE_REVIEW,
+ heldAmountUsd: base.amountUsd,
+ reasons: [
+ "private or restricted project usage needs admin visibility review before chargeback",
+ ],
+ actions: [
+ "Show aggregate spend only",
+ "Suppress project title from department dashboard",
+ "Require admin approval before grant closeout export",
+ ],
+ };
+ }
+
+ return {
+ ...base,
+ status: APPROVED,
+ approvedAmountUsd: base.amountUsd,
+ reasons: [
+ "usage evidence, grant window, cost center, usage type, and owner all match",
+ ],
+ actions: [
+ "Publish in admin dashboard allocation",
+ "Include in grant chargeback export packet",
+ ],
+ };
+}
+
+function analyzeEnterpriseUsage(packet, options = {}) {
+ requireFields(packet, ["labs", "grants", "usageRecords"], "enterprise packet");
+
+ const context = {
+ labs: buildLookup(packet.labs, "labId"),
+ grants: buildLookup(packet.grants, "grantId"),
+ };
+ const asOf = parseDate(options.asOf || packet.asOf || new Date().toISOString(), "asOf");
+ const decisions = packet.usageRecords.map((record) => evaluateUsage(record, context));
+ const totals = decisions.reduce(
+ (acc, decision) => {
+ acc.totalRecords += 1;
+ acc.totalAmountUsd += decision.amountUsd;
+ acc.approvedAmountUsd += decision.approvedAmountUsd;
+ acc.heldAmountUsd += decision.heldAmountUsd;
+ acc.reallocatedAmountUsd += decision.reallocatedAmountUsd;
+ acc.byStatus[decision.status] = (acc.byStatus[decision.status] || 0) + 1;
+ return acc;
+ },
+ {
+ totalRecords: 0,
+ totalAmountUsd: 0,
+ approvedAmountUsd: 0,
+ heldAmountUsd: 0,
+ reallocatedAmountUsd: 0,
+ byStatus: {},
+ }
+ );
+
+ const result = {
+ asOf: asOf.toISOString(),
+ totals,
+ decisions,
+ };
+
+ return {
+ ...result,
+ auditDigest: createAuditDigest(result),
+ };
+}
+
+function renderMarkdownReport(result) {
+ const lines = [
+ "# Enterprise Usage Cost Allocation Guard",
+ "",
+ `As of: ${result.asOf}`,
+ `Audit digest: \`${result.auditDigest}\``,
+ "",
+ "## Totals",
+ "",
+ `- Usage records: ${result.totals.totalRecords}`,
+ `- Total usage amount: $${result.totals.totalAmountUsd.toFixed(2)}`,
+ `- Approved amount: $${result.totals.approvedAmountUsd.toFixed(2)}`,
+ `- Held amount: $${result.totals.heldAmountUsd.toFixed(2)}`,
+ `- Reallocated amount: $${result.totals.reallocatedAmountUsd.toFixed(2)}`,
+ ];
+
+ for (const [status, count] of Object.entries(result.totals.byStatus).sort()) {
+ lines.push(`- ${status}: ${count}`);
+ }
+
+ lines.push("", "## Allocation Decisions", "");
+ for (const decision of result.decisions) {
+ lines.push(
+ `### ${decision.id}`,
+ "",
+ `- Project: ${decision.projectId}`,
+ `- Lab: ${decision.labId}`,
+ `- Grant: ${decision.grantId}`,
+ `- Amount: $${decision.amountUsd.toFixed(2)}`,
+ `- Status: ${decision.status}`,
+ `- Reasons: ${decision.reasons.join("; ")}`,
+ `- Actions: ${decision.actions.join("; ")}`,
+ ""
+ );
+ }
+
+ return `${lines.join("\n").trimEnd()}\n`;
+}
+
+function escapeXml(value) {
+ return String(value)
+ .replace(/&/g, "&")
+ .replace(//g, ">")
+ .replace(/"/g, """);
+}
+
+function renderSvgSummary(result) {
+ const statuses = Object.entries(result.totals.byStatus).sort();
+ const rows = statuses
+ .map(([status, count], index) => {
+ const y = 160 + index * 46;
+ const width = Math.max(44, count * 92);
+ return `${escapeXml(status)}
+
+ ${count}`;
+ })
+ .join("\n ");
+
+ return `
+`;
+}
+
+module.exports = {
+ APPROVED,
+ HOLD,
+ REALLOCATE,
+ PRIVATE_REVIEW,
+ analyzeEnterpriseUsage,
+ createAuditDigest,
+ renderMarkdownReport,
+ renderSvgSummary,
+};
diff --git a/enterprise-usage-cost-allocation-guard/package.json b/enterprise-usage-cost-allocation-guard/package.json
new file mode 100644
index 00000000..812e53d4
--- /dev/null
+++ b/enterprise-usage-cost-allocation-guard/package.json
@@ -0,0 +1,13 @@
+{
+ "name": "enterprise-usage-cost-allocation-guard",
+ "version": "1.0.0",
+ "private": true,
+ "description": "Synthetic enterprise usage cost-allocation and grant chargeback guard for SCIBASE issue #19.",
+ "main": "index.js",
+ "scripts": {
+ "check": "node --check index.js && node --check sample-data.js && node --check demo.js && node --check test.js",
+ "test": "node test.js",
+ "demo": "node demo.js"
+ },
+ "license": "MIT"
+}
diff --git a/enterprise-usage-cost-allocation-guard/reports/demo.mp4 b/enterprise-usage-cost-allocation-guard/reports/demo.mp4
new file mode 100644
index 00000000..88fbd066
Binary files /dev/null and b/enterprise-usage-cost-allocation-guard/reports/demo.mp4 differ
diff --git a/enterprise-usage-cost-allocation-guard/reports/reviewer-packet.md b/enterprise-usage-cost-allocation-guard/reports/reviewer-packet.md
new file mode 100644
index 00000000..8a8afa7f
--- /dev/null
+++ b/enterprise-usage-cost-allocation-guard/reports/reviewer-packet.md
@@ -0,0 +1,88 @@
+# Enterprise Usage Cost Allocation Guard
+
+As of: 2026-05-22T12:00:00.000Z
+Audit digest: `d5139452c6df2c02f396578ad66c0441c4a3238307cf314683161bba42b25da7`
+
+## Totals
+
+- Usage records: 7
+- Total usage amount: $1965.00
+- Approved amount: $715.00
+- Held amount: $940.00
+- Reallocated amount: $310.00
+- approved: 2
+- hold: 2
+- private_review: 2
+- reallocate: 1
+
+## Allocation Decisions
+
+### usage-001
+
+- Project: proj-vision-atlas
+- Lab: lab-neuro
+- Grant: grant-nih-r01-vision
+- Amount: $620.00
+- Status: approved
+- Reasons: usage evidence, grant window, cost center, usage type, and owner all match
+- Actions: Publish in admin dashboard allocation; Include in grant chargeback export packet
+
+### usage-002
+
+- Project: proj-retina-review
+- Lab: lab-neuro
+- Grant: grant-nih-r01-vision
+- Amount: $260.00
+- Status: private_review
+- Reasons: private or restricted project usage needs admin visibility review before chargeback
+- Actions: Show aggregate spend only; Suppress project title from department dashboard; Require admin approval before grant closeout export
+
+### usage-003
+
+- Project: proj-carbon-sim
+- Lab: lab-climate
+- Grant: grant-nsf-climate-open
+- Amount: $95.00
+- Status: approved
+- Reasons: usage evidence, grant window, cost center, usage type, and owner all match
+- Actions: Publish in admin dashboard allocation; Include in grant chargeback export packet
+
+### usage-004
+
+- Project: proj-carbon-sim
+- Lab: lab-climate
+- Grant: grant-nsf-climate-open
+- Amount: $310.00
+- Status: reallocate
+- Reasons: grant does not permit this cost center or usage type
+- Actions: Remove from grant spend until reallocated; Suggest a permitted lab cost center before dashboard publication
+
+### usage-005
+
+- Project: proj-old-retina
+- Lab: lab-neuro
+- Grant: grant-nih-r01-vision
+- Amount: $420.00
+- Status: hold
+- Reasons: usage occurred outside the grant funding window
+- Actions: Exclude from automated grant chargeback; Route to enterprise finance review
+
+### usage-006
+
+- Project: proj-ocean-private
+- Lab: lab-climate
+- Grant: grant-nsf-climate-open
+- Amount: $180.00
+- Status: private_review
+- Reasons: private or restricted project usage needs admin visibility review before chargeback
+- Actions: Show aggregate spend only; Suppress project title from department dashboard; Require admin approval before grant closeout export
+
+### usage-007
+
+- Project: proj-vision-atlas
+- Lab: lab-neuro
+- Grant: grant-nih-r01-vision
+- Amount: $80.00
+- Status: hold
+- Reasons: usage record has no immutable job/export evidence
+- Actions: Hold chargeback from dashboard totals; Request missing lab, grant, amount, or evidence mapping
diff --git a/enterprise-usage-cost-allocation-guard/reports/summary.json b/enterprise-usage-cost-allocation-guard/reports/summary.json
new file mode 100644
index 00000000..a05dfe0c
--- /dev/null
+++ b/enterprise-usage-cost-allocation-guard/reports/summary.json
@@ -0,0 +1,175 @@
+{
+ "asOf": "2026-05-22T12:00:00.000Z",
+ "totals": {
+ "totalRecords": 7,
+ "totalAmountUsd": 1965,
+ "approvedAmountUsd": 715,
+ "heldAmountUsd": 940,
+ "reallocatedAmountUsd": 310,
+ "byStatus": {
+ "approved": 2,
+ "private_review": 2,
+ "reallocate": 1,
+ "hold": 2
+ }
+ },
+ "decisions": [
+ {
+ "id": "usage-001",
+ "labId": "lab-neuro",
+ "projectId": "proj-vision-atlas",
+ "projectVisibility": "institutional",
+ "usageType": "compute",
+ "amountUsd": 620,
+ "costCenterId": "cc-neuro-grant",
+ "grantId": "grant-nih-r01-vision",
+ "ownerId": "owner-ada",
+ "approvedAmountUsd": 620,
+ "heldAmountUsd": 0,
+ "reallocatedAmountUsd": 0,
+ "reasons": [
+ "usage evidence, grant window, cost center, usage type, and owner all match"
+ ],
+ "actions": [
+ "Publish in admin dashboard allocation",
+ "Include in grant chargeback export packet"
+ ],
+ "status": "approved"
+ },
+ {
+ "id": "usage-002",
+ "labId": "lab-neuro",
+ "projectId": "proj-retina-review",
+ "projectVisibility": "private",
+ "usageType": "ai_review",
+ "amountUsd": 260,
+ "costCenterId": "cc-neuro-grant",
+ "grantId": "grant-nih-r01-vision",
+ "ownerId": "owner-omar",
+ "approvedAmountUsd": 0,
+ "heldAmountUsd": 260,
+ "reallocatedAmountUsd": 0,
+ "reasons": [
+ "private or restricted project usage needs admin visibility review before chargeback"
+ ],
+ "actions": [
+ "Show aggregate spend only",
+ "Suppress project title from department dashboard",
+ "Require admin approval before grant closeout export"
+ ],
+ "status": "private_review"
+ },
+ {
+ "id": "usage-003",
+ "labId": "lab-climate",
+ "projectId": "proj-carbon-sim",
+ "projectVisibility": "public",
+ "usageType": "compute",
+ "amountUsd": 95,
+ "costCenterId": "cc-climate-hpc",
+ "grantId": "grant-nsf-climate-open",
+ "ownerId": "owner-mei",
+ "approvedAmountUsd": 95,
+ "heldAmountUsd": 0,
+ "reallocatedAmountUsd": 0,
+ "reasons": [
+ "usage evidence, grant window, cost center, usage type, and owner all match"
+ ],
+ "actions": [
+ "Publish in admin dashboard allocation",
+ "Include in grant chargeback export packet"
+ ],
+ "status": "approved"
+ },
+ {
+ "id": "usage-004",
+ "labId": "lab-climate",
+ "projectId": "proj-carbon-sim",
+ "projectVisibility": "public",
+ "usageType": "submission",
+ "amountUsd": 310,
+ "costCenterId": "cc-climate-core",
+ "grantId": "grant-nsf-climate-open",
+ "ownerId": "owner-mei",
+ "approvedAmountUsd": 0,
+ "heldAmountUsd": 0,
+ "reallocatedAmountUsd": 310,
+ "reasons": [
+ "grant does not permit this cost center or usage type"
+ ],
+ "actions": [
+ "Remove from grant spend until reallocated",
+ "Suggest a permitted lab cost center before dashboard publication"
+ ],
+ "status": "reallocate"
+ },
+ {
+ "id": "usage-005",
+ "labId": "lab-neuro",
+ "projectId": "proj-old-retina",
+ "projectVisibility": "institutional",
+ "usageType": "storage",
+ "amountUsd": 420,
+ "costCenterId": "cc-neuro-grant",
+ "grantId": "grant-nih-r01-vision",
+ "ownerId": "owner-ada",
+ "approvedAmountUsd": 0,
+ "heldAmountUsd": 420,
+ "reallocatedAmountUsd": 0,
+ "reasons": [
+ "usage occurred outside the grant funding window"
+ ],
+ "actions": [
+ "Exclude from automated grant chargeback",
+ "Route to enterprise finance review"
+ ],
+ "status": "hold"
+ },
+ {
+ "id": "usage-006",
+ "labId": "lab-climate",
+ "projectId": "proj-ocean-private",
+ "projectVisibility": "private",
+ "usageType": "storage",
+ "amountUsd": 180,
+ "costCenterId": "cc-climate-hpc",
+ "grantId": "grant-nsf-climate-open",
+ "ownerId": "owner-mei",
+ "approvedAmountUsd": 0,
+ "heldAmountUsd": 180,
+ "reallocatedAmountUsd": 0,
+ "reasons": [
+ "private or restricted project usage needs admin visibility review before chargeback"
+ ],
+ "actions": [
+ "Show aggregate spend only",
+ "Suppress project title from department dashboard",
+ "Require admin approval before grant closeout export"
+ ],
+ "status": "private_review"
+ },
+ {
+ "id": "usage-007",
+ "labId": "lab-neuro",
+ "projectId": "proj-vision-atlas",
+ "projectVisibility": "public",
+ "usageType": "ai_review",
+ "amountUsd": 80,
+ "costCenterId": "cc-neuro-grant",
+ "grantId": "grant-nih-r01-vision",
+ "ownerId": "owner-ada",
+ "approvedAmountUsd": 0,
+ "heldAmountUsd": 80,
+ "reallocatedAmountUsd": 0,
+ "reasons": [
+ "usage record has no immutable job/export evidence"
+ ],
+ "actions": [
+ "Hold chargeback from dashboard totals",
+ "Request missing lab, grant, amount, or evidence mapping"
+ ],
+ "status": "hold"
+ }
+ ],
+ "auditDigest": "d5139452c6df2c02f396578ad66c0441c4a3238307cf314683161bba42b25da7"
+}
diff --git a/enterprise-usage-cost-allocation-guard/reports/summary.svg b/enterprise-usage-cost-allocation-guard/reports/summary.svg
new file mode 100644
index 00000000..fce5f76b
--- /dev/null
+++ b/enterprise-usage-cost-allocation-guard/reports/summary.svg
@@ -0,0 +1,29 @@
+
diff --git a/enterprise-usage-cost-allocation-guard/requirements-map.md b/enterprise-usage-cost-allocation-guard/requirements-map.md
new file mode 100644
index 00000000..4288327a
--- /dev/null
+++ b/enterprise-usage-cost-allocation-guard/requirements-map.md
@@ -0,0 +1,19 @@
+# Requirements Map
+
+| Issue #19 area | Coverage |
+| --- | --- |
+| Admin dashboards | Produces allocation, hold, reallocation, and private-review totals for institutional admins. |
+| Usage stats | Evaluates storage, compute, submission, and AI-review spend records. |
+| Compliance tracking | Blocks chargeback when grant windows, cost centers, owner sponsorship, or evidence are invalid. |
+| Custom tags and internal initiatives | Preserves lab, grant, cost-center, project, and owner IDs for dashboard rollups. |
+| Export pipelines | Emits reviewer packets suitable for grant closeout and chargeback export review. |
+| API/webhooks | Outputs deterministic decisions and audit digests that can back future signed events. |
+
+## Non-overlap
+
+This is not a broad dashboard, export, webhook replay, compliance evidence,
+identity provisioning, retention/legal hold, data residency, SLA, secret
+rotation, quota, API-change, connector certification, incident, funder reporting,
+AI-model governance, dashboard attribution, initiative-tag, policy-exception,
+IRB, data-export approval, SCIM, deposit reconciliation, or admin notification
+slice. It focuses specifically on usage cost allocation and chargeback safety.
diff --git a/enterprise-usage-cost-allocation-guard/sample-data.js b/enterprise-usage-cost-allocation-guard/sample-data.js
new file mode 100644
index 00000000..21c43b2a
--- /dev/null
+++ b/enterprise-usage-cost-allocation-guard/sample-data.js
@@ -0,0 +1,132 @@
+const sampleEnterpriseUsagePacket = {
+ asOf: "2026-05-22T12:00:00.000Z",
+ labs: [
+ {
+ labId: "lab-neuro",
+ name: "Neuroimaging Methods Lab",
+ activeCostCenters: ["cc-neuro-core", "cc-neuro-grant"],
+ ownerIds: ["owner-ada", "owner-omar"],
+ },
+ {
+ labId: "lab-climate",
+ name: "Climate Model Evaluation Unit",
+ activeCostCenters: ["cc-climate-core", "cc-climate-hpc"],
+ ownerIds: ["owner-mei"],
+ },
+ ],
+ grants: [
+ {
+ grantId: "grant-nih-r01-vision",
+ funder: "NIH",
+ startsAt: "2026-01-01T00:00:00.000Z",
+ endsAt: "2026-12-31T23:59:59.000Z",
+ allowedCostCenters: ["cc-neuro-grant"],
+ allowedUsageTypes: ["compute", "storage", "ai_review"],
+ },
+ {
+ grantId: "grant-nsf-climate-open",
+ funder: "NSF",
+ startsAt: "2026-03-01T00:00:00.000Z",
+ endsAt: "2027-02-28T23:59:59.000Z",
+ allowedCostCenters: ["cc-climate-hpc"],
+ allowedUsageTypes: ["compute", "storage", "submission"],
+ },
+ ],
+ usageRecords: [
+ {
+ id: "usage-001",
+ labId: "lab-neuro",
+ projectId: "proj-vision-atlas",
+ projectVisibility: "institutional",
+ usageType: "compute",
+ amountUsd: 620,
+ costCenterId: "cc-neuro-grant",
+ grantId: "grant-nih-r01-vision",
+ ownerId: "owner-ada",
+ occurredAt: "2026-05-15T10:30:00.000Z",
+ evidenceIds: ["job-run-778", "budget-line-vision-02"],
+ },
+ {
+ id: "usage-002",
+ labId: "lab-neuro",
+ projectId: "proj-retina-review",
+ projectVisibility: "private",
+ usageType: "ai_review",
+ amountUsd: 260,
+ costCenterId: "cc-neuro-grant",
+ grantId: "grant-nih-r01-vision",
+ ownerId: "owner-omar",
+ occurredAt: "2026-05-18T16:00:00.000Z",
+ evidenceIds: ["review-batch-443"],
+ privateDataClass: "human_subjects",
+ },
+ {
+ id: "usage-003",
+ labId: "lab-climate",
+ projectId: "proj-carbon-sim",
+ projectVisibility: "public",
+ usageType: "compute",
+ amountUsd: 95,
+ costCenterId: "cc-climate-hpc",
+ grantId: "grant-nsf-climate-open",
+ ownerId: "owner-mei",
+ occurredAt: "2026-05-20T08:10:00.000Z",
+ evidenceIds: ["hpc-job-226", "repository-export-59"],
+ },
+ {
+ id: "usage-004",
+ labId: "lab-climate",
+ projectId: "proj-carbon-sim",
+ projectVisibility: "public",
+ usageType: "submission",
+ amountUsd: 310,
+ costCenterId: "cc-climate-core",
+ grantId: "grant-nsf-climate-open",
+ ownerId: "owner-mei",
+ occurredAt: "2026-05-21T11:00:00.000Z",
+ evidenceIds: ["submission-export-104"],
+ },
+ {
+ id: "usage-005",
+ labId: "lab-neuro",
+ projectId: "proj-old-retina",
+ projectVisibility: "institutional",
+ usageType: "storage",
+ amountUsd: 420,
+ costCenterId: "cc-neuro-grant",
+ grantId: "grant-nih-r01-vision",
+ ownerId: "owner-ada",
+ occurredAt: "2025-12-20T09:00:00.000Z",
+ evidenceIds: ["bucket-archive-91"],
+ },
+ {
+ id: "usage-006",
+ labId: "lab-climate",
+ projectId: "proj-ocean-private",
+ projectVisibility: "private",
+ usageType: "storage",
+ amountUsd: 180,
+ costCenterId: "cc-climate-hpc",
+ grantId: "grant-nsf-climate-open",
+ ownerId: "owner-mei",
+ occurredAt: "2026-05-21T15:30:00.000Z",
+ evidenceIds: ["restricted-bucket-12"],
+ privateDataClass: "embargoed_partner_data",
+ },
+ {
+ id: "usage-007",
+ labId: "lab-neuro",
+ projectId: "proj-vision-atlas",
+ projectVisibility: "public",
+ usageType: "ai_review",
+ amountUsd: 80,
+ costCenterId: "cc-neuro-grant",
+ grantId: "grant-nih-r01-vision",
+ ownerId: "owner-ada",
+ occurredAt: "2026-05-22T09:00:00.000Z",
+ evidenceIds: [],
+ },
+ ],
+};
+
+module.exports = { sampleEnterpriseUsagePacket };
diff --git a/enterprise-usage-cost-allocation-guard/test.js b/enterprise-usage-cost-allocation-guard/test.js
new file mode 100644
index 00000000..828932e6
--- /dev/null
+++ b/enterprise-usage-cost-allocation-guard/test.js
@@ -0,0 +1,46 @@
+const assert = require("node:assert/strict");
+const {
+ APPROVED,
+ HOLD,
+ PRIVATE_REVIEW,
+ REALLOCATE,
+ analyzeEnterpriseUsage,
+ createAuditDigest,
+ renderMarkdownReport,
+ renderSvgSummary,
+} = require("./index");
+const { sampleEnterpriseUsagePacket } = require("./sample-data");
+
+const result = analyzeEnterpriseUsage(sampleEnterpriseUsagePacket);
+
+assert.equal(result.totals.totalRecords, 7);
+assert.equal(result.totals.totalAmountUsd, 1965);
+assert.equal(result.totals.approvedAmountUsd, 715);
+assert.equal(result.totals.heldAmountUsd, 940);
+assert.equal(result.totals.reallocatedAmountUsd, 310);
+assert.equal(result.totals.byStatus[APPROVED], 2);
+assert.equal(result.totals.byStatus[HOLD], 2);
+assert.equal(result.totals.byStatus[PRIVATE_REVIEW], 2);
+assert.equal(result.totals.byStatus[REALLOCATE], 1);
+
+const outsideWindow = result.decisions.find((decision) => decision.id === "usage-005");
+assert.equal(outsideWindow.status, HOLD);
+assert.match(outsideWindow.reasons.join(" "), /outside the grant funding window/);
+
+const privateUsage = result.decisions.find((decision) => decision.id === "usage-002");
+assert.equal(privateUsage.status, PRIVATE_REVIEW);
+assert.match(privateUsage.actions.join(" "), /Suppress project title/);
+
+const reallocated = result.decisions.find((decision) => decision.id === "usage-004");
+assert.equal(reallocated.status, REALLOCATE);
+
+assert.equal(createAuditDigest(result), result.auditDigest);
+assert.match(renderMarkdownReport(result), /Enterprise Usage Cost Allocation Guard/);
+assert.match(renderSvgSummary(result), /Approved \$715.00/);
+
+assert.throws(
+ () => analyzeEnterpriseUsage({ labs: [], grants: [], usageRecords: [{}] }),
+ /missing required usage field: id/
+);
+
+console.log("enterprise usage cost allocation guard tests passed");