diff --git a/revenue-commitment-trueup-guard/README.md b/revenue-commitment-trueup-guard/README.md
new file mode 100644
index 00000000..71815f5e
--- /dev/null
+++ b/revenue-commitment-trueup-guard/README.md
@@ -0,0 +1,39 @@
+# Revenue Commitment True-Up Guard
+
+This module adds a synthetic, dependency-free release guard for enterprise annual
+minimum commitments before a revenue true-up invoice is released.
+
+It focuses on the Revenue Infrastructure issue slice that sits after usage
+metering and before invoice release:
+
+- annual or multi-month minimum commitment drawdown
+- true-up amount reconciliation
+- duplicate true-up window detection
+- unsigned or out-of-period amendment impact
+- SLA credit evidence and cap checks
+- overage evidence before billing above the commitment
+
+No credentials, payment processors, real customers, private billing data,
+external APIs, or payout systems are used. All scenarios are synthetic fixtures.
+
+## Run
+
+```bash
+node revenue-commitment-trueup-guard/test.js
+node revenue-commitment-trueup-guard/demo.js
+```
+
+## Reviewer Artifacts
+
+Running `demo.js` writes:
+
+- `artifacts/demo-output.json`
+- `artifacts/reviewer-report.md`
+- `artifacts/trueup-risk-map.svg`
+- `artifacts/commitment-trueup-guard-demo.mp4`
+
+## Actions
+
+- `RELEASE_INVOICE`: commitment, true-up, credits, and evidence are aligned.
+- `REVIEW_BEFORE_RELEASE`: non-blocking finance review is needed.
+- `HOLD_INVOICE`: invoice should not be released until a blocker is resolved.
diff --git a/revenue-commitment-trueup-guard/artifacts/commitment-trueup-guard-demo.mp4 b/revenue-commitment-trueup-guard/artifacts/commitment-trueup-guard-demo.mp4
new file mode 100644
index 00000000..8820a69c
Binary files /dev/null and b/revenue-commitment-trueup-guard/artifacts/commitment-trueup-guard-demo.mp4 differ
diff --git a/revenue-commitment-trueup-guard/artifacts/demo-output.json b/revenue-commitment-trueup-guard/artifacts/demo-output.json
new file mode 100644
index 00000000..b7751913
--- /dev/null
+++ b/revenue-commitment-trueup-guard/artifacts/demo-output.json
@@ -0,0 +1,233 @@
+{
+ "generatedAt": "2026-06-04T07:17:36.824Z",
+ "portfolio": "synthetic-enterprise-commitments",
+ "summary": {
+ "contracts": 6,
+ "invoices": 7,
+ "release": 2,
+ "review": 1,
+ "hold": 4,
+ "totalVarianceCents": 420000,
+ "topFindings": {
+ "TRUEUP_VARIANCE": 4,
+ "DUPLICATE_TRUEUP_WINDOW": 1,
+ "MISSING_BASE_EVIDENCE": 1,
+ "FUTURE_AMENDMENT_APPLIED_EARLY": 1,
+ "MISSING_SLA_TICKET": 2,
+ "SLA_CREDIT_EXCEEDS_CAP": 1
+ }
+ },
+ "evaluations": [
+ {
+ "contractId": "ENT-CLEAN-2026",
+ "customerSegment": "Institutional license",
+ "currency": "USD",
+ "action": "RELEASE_INVOICE",
+ "invoices": [
+ {
+ "contractId": "ENT-CLEAN-2026",
+ "invoiceId": "INV-CLEAN-Q1",
+ "trueUpWindow": "2026-Q1",
+ "customerSegment": "Institutional license",
+ "effectiveCommitmentCents": 1200000,
+ "meteredUsageCents": 1475000,
+ "proposedTrueUpCents": 225000,
+ "expectedTrueUpCents": 225000,
+ "varianceCents": 0,
+ "findings": [],
+ "action": "RELEASE_INVOICE",
+ "riskScore": 0
+ }
+ ]
+ },
+ {
+ "contractId": "ENT-UNDERTRUE-2026",
+ "customerSegment": "Lab group account",
+ "currency": "USD",
+ "action": "HOLD_INVOICE",
+ "invoices": [
+ {
+ "contractId": "ENT-UNDERTRUE-2026",
+ "invoiceId": "INV-UNDER-Q1",
+ "trueUpWindow": "2026-Q1",
+ "customerSegment": "Lab group account",
+ "effectiveCommitmentCents": 900000,
+ "meteredUsageCents": 620000,
+ "proposedTrueUpCents": 150000,
+ "expectedTrueUpCents": 280000,
+ "varianceCents": -130000,
+ "findings": [
+ {
+ "severity": "hold",
+ "code": "TRUEUP_VARIANCE",
+ "message": "Proposed true-up $1500.00 differs from expected $2800.00",
+ "amountCents": -130000
+ }
+ ],
+ "action": "HOLD_INVOICE",
+ "riskScore": 40
+ }
+ ]
+ },
+ {
+ "contractId": "ENT-DUPLICATE-2026",
+ "customerSegment": "Enterprise license",
+ "currency": "USD",
+ "action": "HOLD_INVOICE",
+ "invoices": [
+ {
+ "contractId": "ENT-DUPLICATE-2026",
+ "invoiceId": "INV-DUP-Q1-A",
+ "trueUpWindow": "2026-Q1",
+ "customerSegment": "Enterprise license",
+ "effectiveCommitmentCents": 1000000,
+ "meteredUsageCents": 1080000,
+ "proposedTrueUpCents": 80000,
+ "expectedTrueUpCents": 80000,
+ "varianceCents": 0,
+ "findings": [],
+ "action": "RELEASE_INVOICE",
+ "riskScore": 0
+ },
+ {
+ "contractId": "ENT-DUPLICATE-2026",
+ "invoiceId": "INV-DUP-Q1-B",
+ "trueUpWindow": "2026-Q1",
+ "customerSegment": "Enterprise license",
+ "effectiveCommitmentCents": 1000000,
+ "meteredUsageCents": 1015000,
+ "proposedTrueUpCents": 15000,
+ "expectedTrueUpCents": 15000,
+ "varianceCents": 0,
+ "findings": [
+ {
+ "severity": "hold",
+ "code": "DUPLICATE_TRUEUP_WINDOW",
+ "message": "2026-Q1 is already represented by another invoice",
+ "amountCents": 0
+ }
+ ],
+ "action": "HOLD_INVOICE",
+ "riskScore": 40
+ }
+ ]
+ },
+ {
+ "contractId": "ENT-AMENDMENT-2026",
+ "customerSegment": "Consortium account",
+ "currency": "USD",
+ "action": "HOLD_INVOICE",
+ "invoices": [
+ {
+ "contractId": "ENT-AMENDMENT-2026",
+ "invoiceId": "INV-AMEND-Q1",
+ "trueUpWindow": "2026-Q1",
+ "customerSegment": "Consortium account",
+ "effectiveCommitmentCents": 800000,
+ "meteredUsageCents": 610000,
+ "proposedTrueUpCents": 0,
+ "expectedTrueUpCents": 190000,
+ "varianceCents": -190000,
+ "findings": [
+ {
+ "severity": "hold",
+ "code": "MISSING_BASE_EVIDENCE",
+ "message": "signed-amendment evidence is required before release",
+ "amountCents": 0
+ },
+ {
+ "severity": "hold",
+ "code": "TRUEUP_VARIANCE",
+ "message": "Proposed true-up $0.00 differs from expected $1900.00",
+ "amountCents": -190000
+ },
+ {
+ "severity": "hold",
+ "code": "FUTURE_AMENDMENT_APPLIED_EARLY",
+ "message": "Invoice appears to use a future amendment before its effective date",
+ "amountCents": 0
+ }
+ ],
+ "action": "HOLD_INVOICE",
+ "riskScore": 120
+ }
+ ]
+ },
+ {
+ "contractId": "ENT-SLA-2026",
+ "customerSegment": "Enterprise license",
+ "currency": "USD",
+ "action": "HOLD_INVOICE",
+ "invoices": [
+ {
+ "contractId": "ENT-SLA-2026",
+ "invoiceId": "INV-SLA-Q1",
+ "trueUpWindow": "2026-Q1",
+ "customerSegment": "Enterprise license",
+ "effectiveCommitmentCents": 1100000,
+ "meteredUsageCents": 1280000,
+ "proposedTrueUpCents": 100000,
+ "expectedTrueUpCents": 20000,
+ "varianceCents": 80000,
+ "findings": [
+ {
+ "severity": "hold",
+ "code": "TRUEUP_VARIANCE",
+ "message": "Proposed true-up $1000.00 differs from expected $200.00",
+ "amountCents": 80000
+ },
+ {
+ "severity": "review",
+ "code": "MISSING_SLA_TICKET",
+ "message": "SLA credit needs an incident or support ticket reference",
+ "amountCents": 0
+ },
+ {
+ "severity": "hold",
+ "code": "SLA_CREDIT_EXCEEDS_CAP",
+ "message": "SLA credit $1600.00 exceeds cap $1000.00",
+ "amountCents": 60000
+ }
+ ],
+ "action": "HOLD_INVOICE",
+ "riskScore": 95
+ }
+ ]
+ },
+ {
+ "contractId": "ENT-REVIEW-2026",
+ "customerSegment": "Institutional license",
+ "currency": "USD",
+ "action": "REVIEW_BEFORE_RELEASE",
+ "invoices": [
+ {
+ "contractId": "ENT-REVIEW-2026",
+ "invoiceId": "INV-REVIEW-Q1",
+ "trueUpWindow": "2026-Q1",
+ "customerSegment": "Institutional license",
+ "effectiveCommitmentCents": 1200000,
+ "meteredUsageCents": 1300000,
+ "proposedTrueUpCents": 100000,
+ "expectedTrueUpCents": 80000,
+ "varianceCents": 20000,
+ "findings": [
+ {
+ "severity": "review",
+ "code": "TRUEUP_VARIANCE",
+ "message": "Proposed true-up $1000.00 differs from expected $800.00",
+ "amountCents": 20000
+ },
+ {
+ "severity": "review",
+ "code": "MISSING_SLA_TICKET",
+ "message": "SLA credit needs an incident or support ticket reference",
+ "amountCents": 0
+ }
+ ],
+ "action": "REVIEW_BEFORE_RELEASE",
+ "riskScore": 30
+ }
+ ]
+ }
+ ]
+}
diff --git a/revenue-commitment-trueup-guard/artifacts/reviewer-report.md b/revenue-commitment-trueup-guard/artifacts/reviewer-report.md
new file mode 100644
index 00000000..9f6e825a
--- /dev/null
+++ b/revenue-commitment-trueup-guard/artifacts/reviewer-report.md
@@ -0,0 +1,31 @@
+# Revenue Commitment True-Up Guard Report
+
+Generated: 2026-06-04T07:17:36.824Z
+Portfolio: synthetic-enterprise-commitments
+
+## Summary
+
+- Contracts reviewed: 6
+- Invoices reviewed: 7
+- Release: 2
+- Review before release: 1
+- Hold invoice: 4
+- Absolute true-up variance: $4200.00
+
+## Invoice Decisions
+
+| Contract | Invoice | Action | Expected true-up | Proposed true-up | Findings |
+| --- | --- | --- | ---: | ---: | --- |
+| ENT-CLEAN-2026 | INV-CLEAN-Q1 | RELEASE_INVOICE | $2250.00 | $2250.00 | none |
+| ENT-UNDERTRUE-2026 | INV-UNDER-Q1 | HOLD_INVOICE | $2800.00 | $1500.00 | TRUEUP_VARIANCE |
+| ENT-DUPLICATE-2026 | INV-DUP-Q1-A | RELEASE_INVOICE | $800.00 | $800.00 | none |
+| ENT-DUPLICATE-2026 | INV-DUP-Q1-B | HOLD_INVOICE | $150.00 | $150.00 | DUPLICATE_TRUEUP_WINDOW |
+| ENT-AMENDMENT-2026 | INV-AMEND-Q1 | HOLD_INVOICE | $1900.00 | $0.00 | MISSING_BASE_EVIDENCE, TRUEUP_VARIANCE, FUTURE_AMENDMENT_APPLIED_EARLY |
+| ENT-SLA-2026 | INV-SLA-Q1 | HOLD_INVOICE | $200.00 | $1000.00 | TRUEUP_VARIANCE, MISSING_SLA_TICKET, SLA_CREDIT_EXCEEDS_CAP |
+| ENT-REVIEW-2026 | INV-REVIEW-Q1 | REVIEW_BEFORE_RELEASE | $800.00 | $1000.00 | TRUEUP_VARIANCE, MISSING_SLA_TICKET |
+
+## Reviewer Notes
+
+- All data is synthetic and credential-free.
+- `HOLD_INVOICE` means a finance or revenue-ops blocker should be resolved before release.
+- `REVIEW_BEFORE_RELEASE` means release is possible after a documented finance check.
diff --git a/revenue-commitment-trueup-guard/artifacts/trueup-risk-map.svg b/revenue-commitment-trueup-guard/artifacts/trueup-risk-map.svg
new file mode 100644
index 00000000..e183028c
--- /dev/null
+++ b/revenue-commitment-trueup-guard/artifacts/trueup-risk-map.svg
@@ -0,0 +1,40 @@
+
diff --git a/revenue-commitment-trueup-guard/demo.js b/revenue-commitment-trueup-guard/demo.js
new file mode 100644
index 00000000..87184658
--- /dev/null
+++ b/revenue-commitment-trueup-guard/demo.js
@@ -0,0 +1,103 @@
+const fs = require("fs");
+const path = require("path");
+const { centsToDollars, evaluatePortfolio } = require("./guard");
+const portfolio = require("./fixtures/contracts.json");
+
+const artifactsDir = path.join(__dirname, "artifacts");
+fs.mkdirSync(artifactsDir, { recursive: true });
+
+const result = evaluatePortfolio(portfolio);
+
+function writeJson() {
+ fs.writeFileSync(path.join(artifactsDir, "demo-output.json"), `${JSON.stringify(result, null, 2)}\n`);
+}
+
+function writeMarkdown() {
+ const lines = [
+ "# Revenue Commitment True-Up Guard Report",
+ "",
+ `Generated: ${result.generatedAt}`,
+ `Portfolio: ${result.portfolio}`,
+ "",
+ "## Summary",
+ "",
+ `- Contracts reviewed: ${result.summary.contracts}`,
+ `- Invoices reviewed: ${result.summary.invoices}`,
+ `- Release: ${result.summary.release}`,
+ `- Review before release: ${result.summary.review}`,
+ `- Hold invoice: ${result.summary.hold}`,
+ `- Absolute true-up variance: ${centsToDollars(result.summary.totalVarianceCents)}`,
+ "",
+ "## Invoice Decisions",
+ "",
+ "| Contract | Invoice | Action | Expected true-up | Proposed true-up | Findings |",
+ "| --- | --- | --- | ---: | ---: | --- |",
+ ];
+
+ for (const contract of result.evaluations) {
+ for (const invoice of contract.invoices) {
+ const findings = invoice.findings.map((finding) => finding.code).join(", ") || "none";
+ lines.push(
+ `| ${invoice.contractId} | ${invoice.invoiceId} | ${invoice.action} | ${centsToDollars(
+ invoice.expectedTrueUpCents
+ )} | ${centsToDollars(invoice.proposedTrueUpCents)} | ${findings} |`
+ );
+ }
+ }
+
+ lines.push(
+ "",
+ "## Reviewer Notes",
+ "",
+ "- All data is synthetic and credential-free.",
+ "- `HOLD_INVOICE` means a finance or revenue-ops blocker should be resolved before release.",
+ "- `REVIEW_BEFORE_RELEASE` means release is possible after a documented finance check."
+ );
+
+ fs.writeFileSync(path.join(artifactsDir, "reviewer-report.md"), `${lines.join("\n")}\n`);
+}
+
+function escapeXml(value) {
+ return String(value).replace(/&/g, "&").replace(//g, ">");
+}
+
+function writeSvg() {
+ const invoices = result.evaluations.flatMap((contract) => contract.invoices);
+ const width = 920;
+ const rowHeight = 62;
+ const height = 110 + invoices.length * rowHeight;
+ const maxRisk = Math.max(1, ...invoices.map((invoice) => invoice.riskScore));
+
+ const rows = invoices
+ .map((invoice, index) => {
+ const y = 82 + index * rowHeight;
+ const barWidth = Math.max(4, Math.round((invoice.riskScore / maxRisk) * 420));
+ const color = invoice.action === "HOLD_INVOICE" ? "#b91c1c" : invoice.action === "REVIEW_BEFORE_RELEASE" ? "#ca8a04" : "#15803d";
+ const label = `${invoice.invoiceId} ${invoice.action}`;
+ return `
+ ${escapeXml(label)}
+
+ ${invoice.riskScore}
+ ${escapeXml(
+ invoice.findings.map((finding) => finding.code).join(", ") || "clear"
+ )}`;
+ })
+ .join("\n");
+
+ const svg = `
+`;
+
+ fs.writeFileSync(path.join(artifactsDir, "trueup-risk-map.svg"), svg);
+}
+
+writeJson();
+writeMarkdown();
+writeSvg();
+
+console.log(`Wrote reviewer artifacts to ${artifactsDir}`);
+console.log(JSON.stringify(result.summary, null, 2));
diff --git a/revenue-commitment-trueup-guard/fixtures/contracts.json b/revenue-commitment-trueup-guard/fixtures/contracts.json
new file mode 100644
index 00000000..1410905a
--- /dev/null
+++ b/revenue-commitment-trueup-guard/fixtures/contracts.json
@@ -0,0 +1,196 @@
+{
+ "generatedAt": "2026-06-04T07:15:00.000Z",
+ "portfolio": "synthetic-enterprise-commitments",
+ "contracts": [
+ {
+ "contractId": "ENT-CLEAN-2026",
+ "customerSegment": "Institutional license",
+ "currency": "USD",
+ "periodStart": "2026-01-01",
+ "periodEnd": "2026-03-31",
+ "minimumCommitmentCents": 1200000,
+ "signedAmendments": [
+ {
+ "amendmentId": "AMD-CLEAN-Q1",
+ "effectiveDate": "2026-01-01",
+ "signedAt": "2025-12-20",
+ "minimumCommitmentCents": 1200000
+ }
+ ],
+ "invoices": [
+ {
+ "invoiceId": "INV-CLEAN-Q1",
+ "trueUpWindow": "2026-Q1",
+ "periodStart": "2026-01-01",
+ "periodEnd": "2026-03-31",
+ "meteredUsageCents": 1475000,
+ "proposedTrueUpCents": 225000,
+ "slaCreditCents": 50000,
+ "slaCreditCapCents": 75000,
+ "evidence": [
+ "usage-meter-export",
+ "signed-amendment",
+ "overage-approval",
+ "sla-credit-ticket"
+ ]
+ }
+ ]
+ },
+ {
+ "contractId": "ENT-UNDERTRUE-2026",
+ "customerSegment": "Lab group account",
+ "currency": "USD",
+ "periodStart": "2026-01-01",
+ "periodEnd": "2026-03-31",
+ "minimumCommitmentCents": 900000,
+ "signedAmendments": [
+ {
+ "amendmentId": "AMD-UNDER-Q1",
+ "effectiveDate": "2026-01-01",
+ "signedAt": "2025-12-15",
+ "minimumCommitmentCents": 900000
+ }
+ ],
+ "invoices": [
+ {
+ "invoiceId": "INV-UNDER-Q1",
+ "trueUpWindow": "2026-Q1",
+ "periodStart": "2026-01-01",
+ "periodEnd": "2026-03-31",
+ "meteredUsageCents": 620000,
+ "proposedTrueUpCents": 150000,
+ "slaCreditCents": 0,
+ "slaCreditCapCents": 50000,
+ "evidence": ["usage-meter-export", "signed-amendment"]
+ }
+ ]
+ },
+ {
+ "contractId": "ENT-DUPLICATE-2026",
+ "customerSegment": "Enterprise license",
+ "currency": "USD",
+ "periodStart": "2026-01-01",
+ "periodEnd": "2026-03-31",
+ "minimumCommitmentCents": 1000000,
+ "signedAmendments": [
+ {
+ "amendmentId": "AMD-DUP-Q1",
+ "effectiveDate": "2026-01-01",
+ "signedAt": "2025-12-11",
+ "minimumCommitmentCents": 1000000
+ }
+ ],
+ "invoices": [
+ {
+ "invoiceId": "INV-DUP-Q1-A",
+ "trueUpWindow": "2026-Q1",
+ "periodStart": "2026-01-01",
+ "periodEnd": "2026-03-31",
+ "meteredUsageCents": 1080000,
+ "proposedTrueUpCents": 80000,
+ "slaCreditCents": 0,
+ "slaCreditCapCents": 50000,
+ "evidence": ["usage-meter-export", "signed-amendment", "overage-approval"]
+ },
+ {
+ "invoiceId": "INV-DUP-Q1-B",
+ "trueUpWindow": "2026-Q1",
+ "periodStart": "2026-01-01",
+ "periodEnd": "2026-03-31",
+ "meteredUsageCents": 1015000,
+ "proposedTrueUpCents": 15000,
+ "slaCreditCents": 0,
+ "slaCreditCapCents": 50000,
+ "evidence": ["usage-meter-export", "signed-amendment", "overage-approval"]
+ }
+ ]
+ },
+ {
+ "contractId": "ENT-AMENDMENT-2026",
+ "customerSegment": "Consortium account",
+ "currency": "USD",
+ "periodStart": "2026-01-01",
+ "periodEnd": "2026-03-31",
+ "minimumCommitmentCents": 800000,
+ "signedAmendments": [
+ {
+ "amendmentId": "AMD-FUTURE-Q2",
+ "effectiveDate": "2026-04-01",
+ "signedAt": "2026-03-15",
+ "minimumCommitmentCents": 600000
+ }
+ ],
+ "invoices": [
+ {
+ "invoiceId": "INV-AMEND-Q1",
+ "trueUpWindow": "2026-Q1",
+ "periodStart": "2026-01-01",
+ "periodEnd": "2026-03-31",
+ "meteredUsageCents": 610000,
+ "proposedTrueUpCents": 0,
+ "slaCreditCents": 0,
+ "slaCreditCapCents": 50000,
+ "evidence": ["usage-meter-export"]
+ }
+ ]
+ },
+ {
+ "contractId": "ENT-SLA-2026",
+ "customerSegment": "Enterprise license",
+ "currency": "USD",
+ "periodStart": "2026-01-01",
+ "periodEnd": "2026-03-31",
+ "minimumCommitmentCents": 1100000,
+ "signedAmendments": [
+ {
+ "amendmentId": "AMD-SLA-Q1",
+ "effectiveDate": "2026-01-01",
+ "signedAt": "2025-12-18",
+ "minimumCommitmentCents": 1100000
+ }
+ ],
+ "invoices": [
+ {
+ "invoiceId": "INV-SLA-Q1",
+ "trueUpWindow": "2026-Q1",
+ "periodStart": "2026-01-01",
+ "periodEnd": "2026-03-31",
+ "meteredUsageCents": 1280000,
+ "proposedTrueUpCents": 100000,
+ "slaCreditCents": 160000,
+ "slaCreditCapCents": 100000,
+ "evidence": ["usage-meter-export", "signed-amendment", "overage-approval"]
+ }
+ ]
+ },
+ {
+ "contractId": "ENT-REVIEW-2026",
+ "customerSegment": "Institutional license",
+ "currency": "USD",
+ "periodStart": "2026-01-01",
+ "periodEnd": "2026-03-31",
+ "minimumCommitmentCents": 1200000,
+ "signedAmendments": [
+ {
+ "amendmentId": "AMD-REVIEW-Q1",
+ "effectiveDate": "2026-01-01",
+ "signedAt": "2025-12-21",
+ "minimumCommitmentCents": 1200000
+ }
+ ],
+ "invoices": [
+ {
+ "invoiceId": "INV-REVIEW-Q1",
+ "trueUpWindow": "2026-Q1",
+ "periodStart": "2026-01-01",
+ "periodEnd": "2026-03-31",
+ "meteredUsageCents": 1300000,
+ "proposedTrueUpCents": 100000,
+ "slaCreditCents": 20000,
+ "slaCreditCapCents": 75000,
+ "evidence": ["usage-meter-export", "signed-amendment", "overage-approval"]
+ }
+ ]
+ }
+ ]
+}
diff --git a/revenue-commitment-trueup-guard/guard.js b/revenue-commitment-trueup-guard/guard.js
new file mode 100644
index 00000000..0ad3c3fd
--- /dev/null
+++ b/revenue-commitment-trueup-guard/guard.js
@@ -0,0 +1,201 @@
+const REQUIRED_EVIDENCE = {
+ base: ["usage-meter-export", "signed-amendment"],
+ overage: "overage-approval",
+ slaCredit: "sla-credit-ticket",
+};
+
+const ACTIONS = {
+ release: "RELEASE_INVOICE",
+ review: "REVIEW_BEFORE_RELEASE",
+ hold: "HOLD_INVOICE",
+};
+
+function centsToDollars(cents) {
+ return `$${(cents / 100).toFixed(2)}`;
+}
+
+function parseDate(value, fieldName) {
+ const date = new Date(`${value}T00:00:00.000Z`);
+ if (Number.isNaN(date.getTime())) {
+ throw new Error(`Invalid ${fieldName}: ${value}`);
+ }
+ return date;
+}
+
+function isDateWithin(value, start, end) {
+ const date = parseDate(value, "date");
+ return date >= parseDate(start, "periodStart") && date <= parseDate(end, "periodEnd");
+}
+
+function getEffectiveCommitment(contract, invoice) {
+ const amendments = Array.isArray(contract.signedAmendments) ? contract.signedAmendments : [];
+ const eligible = amendments
+ .filter((amendment) => amendment.signedAt)
+ .filter((amendment) => isDateWithin(amendment.effectiveDate, invoice.periodStart, invoice.periodEnd))
+ .sort((a, b) => parseDate(b.effectiveDate, "effectiveDate") - parseDate(a.effectiveDate, "effectiveDate"));
+
+ return eligible[0]?.minimumCommitmentCents ?? contract.minimumCommitmentCents;
+}
+
+function expectedTrueUpCents(invoice, effectiveCommitmentCents) {
+ const gross = invoice.meteredUsageCents - effectiveCommitmentCents;
+ const overage = gross > 0 ? gross : 0;
+ const floorCatchUp = gross < 0 ? Math.abs(gross) : 0;
+ return Math.max(0, overage + floorCatchUp - invoice.slaCreditCents);
+}
+
+function addFinding(findings, severity, code, message, amountCents = 0) {
+ findings.push({ severity, code, message, amountCents });
+}
+
+function evaluateInvoice(contract, invoice, seenWindows) {
+ const findings = [];
+ const evidence = new Set(invoice.evidence || []);
+ const effectiveCommitmentCents = getEffectiveCommitment(contract, invoice);
+ const expectedTrueUp = expectedTrueUpCents(invoice, effectiveCommitmentCents);
+ const variance = invoice.proposedTrueUpCents - expectedTrueUp;
+
+ for (const required of REQUIRED_EVIDENCE.base) {
+ if (!evidence.has(required)) {
+ addFinding(findings, "hold", "MISSING_BASE_EVIDENCE", `${required} evidence is required before release`);
+ }
+ }
+
+ if (seenWindows.has(invoice.trueUpWindow)) {
+ addFinding(findings, "hold", "DUPLICATE_TRUEUP_WINDOW", `${invoice.trueUpWindow} is already represented by another invoice`);
+ }
+ seenWindows.add(invoice.trueUpWindow);
+
+ if (variance !== 0) {
+ addFinding(
+ findings,
+ Math.abs(variance) >= 50000 ? "hold" : "review",
+ "TRUEUP_VARIANCE",
+ `Proposed true-up ${centsToDollars(invoice.proposedTrueUpCents)} differs from expected ${centsToDollars(expectedTrueUp)}`,
+ variance
+ );
+ }
+
+ if (invoice.meteredUsageCents > effectiveCommitmentCents && !evidence.has(REQUIRED_EVIDENCE.overage)) {
+ addFinding(findings, "hold", "MISSING_OVERAGE_APPROVAL", "Overage billing requires approval evidence");
+ }
+
+ if (invoice.slaCreditCents > 0 && !evidence.has(REQUIRED_EVIDENCE.slaCredit)) {
+ addFinding(findings, "review", "MISSING_SLA_TICKET", "SLA credit needs an incident or support ticket reference");
+ }
+
+ if (invoice.slaCreditCents > invoice.slaCreditCapCents) {
+ addFinding(
+ findings,
+ "hold",
+ "SLA_CREDIT_EXCEEDS_CAP",
+ `SLA credit ${centsToDollars(invoice.slaCreditCents)} exceeds cap ${centsToDollars(invoice.slaCreditCapCents)}`,
+ invoice.slaCreditCents - invoice.slaCreditCapCents
+ );
+ }
+
+ const hasOnlyFutureAmendment = (contract.signedAmendments || []).some(
+ (amendment) =>
+ amendment.signedAt &&
+ parseDate(amendment.effectiveDate, "effectiveDate") > parseDate(invoice.periodEnd, "periodEnd") &&
+ amendment.minimumCommitmentCents !== contract.minimumCommitmentCents
+ );
+
+ if (hasOnlyFutureAmendment && invoice.proposedTrueUpCents < expectedTrueUp) {
+ addFinding(findings, "hold", "FUTURE_AMENDMENT_APPLIED_EARLY", "Invoice appears to use a future amendment before its effective date");
+ }
+
+ return {
+ contractId: contract.contractId,
+ invoiceId: invoice.invoiceId,
+ trueUpWindow: invoice.trueUpWindow,
+ customerSegment: contract.customerSegment,
+ effectiveCommitmentCents,
+ meteredUsageCents: invoice.meteredUsageCents,
+ proposedTrueUpCents: invoice.proposedTrueUpCents,
+ expectedTrueUpCents: expectedTrueUp,
+ varianceCents: variance,
+ findings,
+ };
+}
+
+function decideAction(findings) {
+ if (findings.some((finding) => finding.severity === "hold")) {
+ return ACTIONS.hold;
+ }
+ if (findings.some((finding) => finding.severity === "review")) {
+ return ACTIONS.review;
+ }
+ return ACTIONS.release;
+}
+
+function riskScore(findings) {
+ return findings.reduce((score, finding) => score + (finding.severity === "hold" ? 40 : 15), 0);
+}
+
+function evaluateContract(contract) {
+ const seenWindows = new Set();
+ const invoices = contract.invoices.map((invoice) => {
+ const result = evaluateInvoice(contract, invoice, seenWindows);
+ return {
+ ...result,
+ action: decideAction(result.findings),
+ riskScore: riskScore(result.findings),
+ };
+ });
+
+ const contractAction = decideAction(invoices.flatMap((invoice) => invoice.findings));
+
+ return {
+ contractId: contract.contractId,
+ customerSegment: contract.customerSegment,
+ currency: contract.currency,
+ action: contractAction,
+ invoices,
+ };
+}
+
+function summarize(evaluations) {
+ const summary = {
+ contracts: evaluations.length,
+ invoices: 0,
+ release: 0,
+ review: 0,
+ hold: 0,
+ totalVarianceCents: 0,
+ topFindings: {},
+ };
+
+ for (const contract of evaluations) {
+ for (const invoice of contract.invoices) {
+ summary.invoices += 1;
+ summary.totalVarianceCents += Math.abs(invoice.varianceCents);
+ if (invoice.action === ACTIONS.release) summary.release += 1;
+ if (invoice.action === ACTIONS.review) summary.review += 1;
+ if (invoice.action === ACTIONS.hold) summary.hold += 1;
+
+ for (const finding of invoice.findings) {
+ summary.topFindings[finding.code] = (summary.topFindings[finding.code] || 0) + 1;
+ }
+ }
+ }
+
+ return summary;
+}
+
+function evaluatePortfolio(portfolio) {
+ const evaluations = portfolio.contracts.map(evaluateContract);
+ return {
+ generatedAt: new Date().toISOString(),
+ portfolio: portfolio.portfolio,
+ summary: summarize(evaluations),
+ evaluations,
+ };
+}
+
+module.exports = {
+ ACTIONS,
+ centsToDollars,
+ evaluatePortfolio,
+ expectedTrueUpCents,
+};
diff --git a/revenue-commitment-trueup-guard/test.js b/revenue-commitment-trueup-guard/test.js
new file mode 100644
index 00000000..a6aea6df
--- /dev/null
+++ b/revenue-commitment-trueup-guard/test.js
@@ -0,0 +1,49 @@
+const assert = require("assert");
+const { ACTIONS, evaluatePortfolio, expectedTrueUpCents } = require("./guard");
+const portfolio = require("./fixtures/contracts.json");
+
+const result = evaluatePortfolio(portfolio);
+const invoices = new Map(
+ result.evaluations.flatMap((contract) => contract.invoices.map((invoice) => [invoice.invoiceId, invoice]))
+);
+
+assert.strictEqual(result.summary.contracts, 6);
+assert.strictEqual(result.summary.invoices, 7);
+assert.strictEqual(result.summary.release, 2);
+assert.strictEqual(result.summary.review, 1);
+assert.strictEqual(result.summary.hold, 4);
+
+assert.strictEqual(invoices.get("INV-CLEAN-Q1").action, ACTIONS.release);
+assert.strictEqual(invoices.get("INV-CLEAN-Q1").expectedTrueUpCents, 225000);
+assert.strictEqual(invoices.get("INV-CLEAN-Q1").proposedTrueUpCents, 225000);
+assert.strictEqual(invoices.get("INV-CLEAN-Q1").findings.length, 0);
+
+assert.strictEqual(invoices.get("INV-UNDER-Q1").action, ACTIONS.hold);
+assert.ok(invoices.get("INV-UNDER-Q1").findings.some((finding) => finding.code === "TRUEUP_VARIANCE"));
+
+assert.strictEqual(invoices.get("INV-DUP-Q1-B").action, ACTIONS.hold);
+assert.ok(invoices.get("INV-DUP-Q1-B").findings.some((finding) => finding.code === "DUPLICATE_TRUEUP_WINDOW"));
+
+assert.strictEqual(invoices.get("INV-AMEND-Q1").action, ACTIONS.hold);
+assert.ok(invoices.get("INV-AMEND-Q1").findings.some((finding) => finding.code === "FUTURE_AMENDMENT_APPLIED_EARLY"));
+
+assert.strictEqual(invoices.get("INV-SLA-Q1").action, ACTIONS.hold);
+assert.ok(invoices.get("INV-SLA-Q1").findings.some((finding) => finding.code === "SLA_CREDIT_EXCEEDS_CAP"));
+assert.ok(invoices.get("INV-SLA-Q1").findings.some((finding) => finding.code === "MISSING_SLA_TICKET"));
+
+assert.strictEqual(invoices.get("INV-REVIEW-Q1").action, ACTIONS.review);
+assert.ok(invoices.get("INV-REVIEW-Q1").findings.some((finding) => finding.code === "TRUEUP_VARIANCE"));
+assert.ok(invoices.get("INV-REVIEW-Q1").findings.some((finding) => finding.code === "MISSING_SLA_TICKET"));
+
+assert.strictEqual(
+ expectedTrueUpCents(
+ {
+ meteredUsageCents: 125000,
+ slaCreditCents: 25000,
+ },
+ 100000
+ ),
+ 0
+);
+
+console.log("revenue-commitment-trueup-guard tests passed");