Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions enterprise-contract-drift-guard/demo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
const fs = require("fs");
const path = require("path");
const { contracts } = require("./sample-data");
const { evaluateContracts } = require("./index");

const outDir = path.join(__dirname, "reports");
fs.mkdirSync(outDir, { recursive: true });

const report = evaluateContracts(contracts);
fs.writeFileSync(path.join(outDir, "contract-drift-report.json"), JSON.stringify(report, null, 2));

const rows = report.reports
.map((entry) => `| ${entry.system} | ${entry.decision} | ${entry.highestSeverity} | ${entry.findings.length} |`)
.join("\n");

fs.writeFileSync(
path.join(outDir, "contract-drift-report.md"),
[
"# Enterprise Contract Drift Guard",
"",
"| System | Decision | Highest severity | Findings |",
"| --- | --- | --- | ---: |",
rows,
"",
`Release holds: ${report.releaseHolds}`,
`Approvals: ${report.approvals}`
].join("\n")
);

fs.writeFileSync(
path.join(outDir, "summary.svg"),
`<svg xmlns="http://www.w3.org/2000/svg" width="760" height="260" role="img" aria-label="Enterprise contract drift summary">
<rect width="760" height="260" fill="#101820"/>
<text x="32" y="48" fill="#f2f7f2" font-family="Arial" font-size="24">Enterprise Contract Drift Guard</text>
<text x="32" y="86" fill="#b6c2c9" font-family="Arial" font-size="15">Synthetic institutional integration contract review</text>
<rect x="32" y="122" width="${report.releaseHolds * 120}" height="42" fill="#d64545"/>
<text x="40" y="149" fill="#fff" font-family="Arial" font-size="16">${report.releaseHolds} release holds</text>
<rect x="32" y="178" width="${Math.max(report.approvals, 1) * 120}" height="42" fill="#35a66f"/>
<text x="40" y="205" fill="#fff" font-family="Arial" font-size="16">${report.approvals} approvals</text>
</svg>`
);

console.log(JSON.stringify({
totalContracts: report.totalContracts,
releaseHolds: report.releaseHolds,
approvals: report.approvals,
findings: report.reports.reduce((sum, entry) => sum + entry.findings.length, 0)
}, null, 2));
122 changes: 122 additions & 0 deletions enterprise-contract-drift-guard/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
function classifyChange(beforeType, afterType) {
if (beforeType === afterType) return "compatible";
if (!afterType) return "removed-field";
if (!beforeType) return "added-field";
if (beforeType.startsWith("enum:") && afterType.startsWith("enum:")) {
const beforeValues = beforeType.slice(5).split("|");
const afterValues = afterType.slice(5).split("|");
return beforeValues.every((value) => afterValues.includes(value))
? "enum-expanded"
: "enum-narrowed";
}
return "type-changed";
}

function evaluateContract(contract) {
const fields = new Set([
...Object.keys(contract.currentSchema),
...Object.keys(contract.proposedSchema)
]);
const findings = [];

for (const field of fields) {
const change = classifyChange(contract.currentSchema[field], contract.proposedSchema[field]);
if (change === "compatible" || change === "added-field") continue;
findings.push({
field,
type: change,
current: contract.currentSchema[field] || null,
proposed: contract.proposedSchema[field] || null,
severity: change === "removed-field" || change === "type-changed" ? "high" : "medium"
});
}

for (const field of contract.requiredFields) {
if (!contract.proposedSchema[field]) {
findings.push({
field,
type: "missing-required-field",
severity: "critical",
current: contract.currentSchema[field] || null,
proposed: null
});
}
}

if (contract.deprecationDays < 90) {
findings.push({
field: "deprecationDays",
type: "short-deprecation-window",
severity: "medium",
current: contract.deprecationDays,
proposed: ">=90"
});
}

if (!contract.payloadPolicy.redactsEmails) {
findings.push({
field: "payloadPolicy.redactsEmails",
type: "pii-redaction-disabled",
severity: "high",
current: false,
proposed: true
});
}

if (contract.payloadPolicy.includesInternalNotes) {
findings.push({
field: "payloadPolicy.includesInternalNotes",
type: "internal-notes-leak-risk",
severity: "high",
current: true,
proposed: false
});
}

const highest = findings.some((finding) => finding.severity === "critical")
? "critical"
: findings.some((finding) => finding.severity === "high")
? "high"
: findings.some((finding) => finding.severity === "medium")
? "medium"
: "clear";

return {
id: contract.id,
system: contract.system,
owner: contract.owner,
currentVersion: contract.currentVersion,
proposedVersion: contract.proposedVersion,
decision: highest === "clear" ? "approve" : highest === "medium" ? "stage-with-notice" : "hold-release",
highestSeverity: highest,
findings,
remediation: findings.map((finding) => remediationFor(finding, contract))
};
}

function remediationFor(finding, contract) {
const actions = {
"removed-field": `Restore ${finding.field} or publish a compatibility alias for ${contract.consumerPinnedVersion}.`,
"missing-required-field": `Keep ${finding.field} in the proposed ${contract.system} payload until all consumers migrate.`,
"type-changed": `Version-gate ${finding.field} and keep the old type available for pinned consumers.`,
"enum-narrowed": `Do not remove enum values without a documented migration and replay fixture.`,
"short-deprecation-window": "Extend the deprecation period to at least 90 days for enterprise integrations.",
"pii-redaction-disabled": "Enable email redaction before exporting review or roster payloads.",
"internal-notes-leak-risk": "Strip internal notes from outbound institutional payloads."
};
return actions[finding.type] || `Review ${finding.field} before release.`;
}

function evaluateContracts(contracts) {
const reports = contracts.map(evaluateContract);
return {
generatedAt: "2026-05-29T00:00:00.000Z",
totalContracts: reports.length,
releaseHolds: reports.filter((report) => report.decision === "hold-release").length,
stagedNotices: reports.filter((report) => report.decision === "stage-with-notice").length,
approvals: reports.filter((report) => report.decision === "approve").length,
reports
};
}

module.exports = { classifyChange, evaluateContract, evaluateContracts };
28 changes: 28 additions & 0 deletions enterprise-contract-drift-guard/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Enterprise Contract Drift Guard

This module is a focused Enterprise Tooling slice for institutional integrations. It reviews proposed API, webhook, and export contract changes before they reach external systems such as DSpace, Invenio, Canvas, Moodle, ELNs, lab inventory tools, HRIS, and ORCID sync jobs.

It is intentionally separate from broad enterprise dashboards, webhook delivery, webhook replay, webhook payload redaction, SCIM deprovisioning, repository sync SLA checks, LMS roster passback, connector certification, vendor DPA review, data residency, and admin alert escalation work.

## What it checks

- Removed required fields in export and webhook payloads
- Type changes that would break pinned enterprise consumers
- Narrowed enum values
- Deprecation windows below 90 days
- Email redaction and internal-note leakage policy
- Versioned remediation actions for institutional integration owners

## Run

```bash
node enterprise-contract-drift-guard/test.js
node enterprise-contract-drift-guard/demo.js
node enterprise-contract-drift-guard/render-video.js
```

Outputs are written to `enterprise-contract-drift-guard/reports/`.

## Safety

All data is synthetic. The module does not call institutional repositories, LMS platforms, ELNs, HRIS tools, ORCID, webhooks, private APIs, credentials, or production SCIBASE services.
38 changes: 38 additions & 0 deletions enterprise-contract-drift-guard/render-video.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
const { execFileSync } = require("child_process");
const fs = require("fs");
const path = require("path");

const outDir = path.join(__dirname, "reports");
fs.mkdirSync(outDir, { recursive: true });

const ppm = path.join(outDir, "demo-frame.ppm");
const mp4 = path.join(outDir, "demo.mp4");
const width = 960;
const height = 540;
const pixels = [];

for (let y = 0; y < height; y += 1) {
for (let x = 0; x < width; x += 1) {
const inPanel = x > 80 && x < 880 && y > 90 && y < 450;
const bar = y > 210 && y < 260 && x > 150 && x < 650;
const okBar = y > 310 && y < 360 && x > 150 && x < 310;
const r = inPanel ? (bar ? 214 : okBar ? 53 : 26) : 9;
const g = inPanel ? (bar ? 69 : okBar ? 166 : 40) : 14;
const b = inPanel ? (bar ? 69 : okBar ? 111 : 53) : 22;
pixels.push(String.fromCharCode(r, g, b));
}
}

fs.writeFileSync(ppm, `P6\n${width} ${height}\n255\n` + pixels.join(""), "binary");
execFileSync("ffmpeg", [
"-y",
"-loop", "1",
"-framerate", "24",
"-i", ppm,
"-t", "5",
"-vf", "format=yuv420p",
"-movflags", "+faststart",
mp4
], { stdio: "inherit" });

console.log(mp4);
115 changes: 115 additions & 0 deletions enterprise-contract-drift-guard/reports/contract-drift-report.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
{
"generatedAt": "2026-05-29T00:00:00.000Z",
"totalContracts": 4,
"releaseHolds": 3,
"stagedNotices": 0,
"approvals": 1,
"reports": [
{
"id": "dspace-repository-export",
"system": "DSpace",
"owner": "University Library",
"currentVersion": "2026.05",
"proposedVersion": "2026.06",
"decision": "hold-release",
"highestSeverity": "critical",
"findings": [
{
"field": "license",
"type": "type-changed",
"current": "string",
"proposed": "enum:cc-by|cc0",
"severity": "high"
},
{
"field": "embargoUntil",
"type": "removed-field",
"current": "date",
"proposed": null,
"severity": "high"
},
{
"field": "embargoUntil",
"type": "missing-required-field",
"severity": "critical",
"current": "date",
"proposed": null
}
],
"remediation": [
"Version-gate license and keep the old type available for pinned consumers.",
"Restore embargoUntil or publish a compatibility alias for 2026.05.",
"Keep embargoUntil in the proposed DSpace payload until all consumers migrate."
]
},
{
"id": "canvas-course-publication-webhook",
"system": "Canvas",
"owner": "Graduate School",
"currentVersion": "2026.04",
"proposedVersion": "2026.06",
"decision": "hold-release",
"highestSeverity": "high",
"findings": [
{
"field": "courseId",
"type": "type-changed",
"current": "string",
"proposed": "number",
"severity": "high"
},
{
"field": "eventType",
"type": "enum-narrowed",
"current": "enum:created|published|reviewed",
"proposed": "enum:created|published|retracted",
"severity": "medium"
},
{
"field": "deprecationDays",
"type": "short-deprecation-window",
"severity": "medium",
"current": 30,
"proposed": ">=90"
}
],
"remediation": [
"Version-gate courseId and keep the old type available for pinned consumers.",
"Do not remove enum values without a documented migration and replay fixture.",
"Extend the deprecation period to at least 90 days for enterprise integrations."
]
},
{
"id": "orcid-hris-identity-sync",
"system": "ORCID-HRIS",
"owner": "Research HR",
"currentVersion": "2026.03",
"proposedVersion": "2026.06",
"decision": "approve",
"highestSeverity": "clear",
"findings": [],
"remediation": []
},
{
"id": "eln-inventory-review-export",
"system": "ELN",
"owner": "Lab Operations",
"currentVersion": "2026.05",
"proposedVersion": "2026.06",
"decision": "hold-release",
"highestSeverity": "high",
"findings": [
{
"field": "payloadPolicy.redactsEmails",
"type": "pii-redaction-disabled",
"severity": "high",
"current": false,
"proposed": true
}
],
"remediation": [
"Enable email redaction before exporting review or roster payloads."
]
}
]
}
11 changes: 11 additions & 0 deletions enterprise-contract-drift-guard/reports/contract-drift-report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Enterprise Contract Drift Guard

| System | Decision | Highest severity | Findings |
| --- | --- | --- | ---: |
| DSpace | hold-release | critical | 3 |
| Canvas | hold-release | high | 3 |
| ORCID-HRIS | approve | clear | 0 |
| ELN | hold-release | high | 1 |

Release holds: 3
Approvals: 1
Binary file added enterprise-contract-drift-guard/reports/demo.mp4
Binary file not shown.
9 changes: 9 additions & 0 deletions enterprise-contract-drift-guard/reports/summary.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading