diff --git a/revenue-refund-liability-reserve-guard/README.md b/revenue-refund-liability-reserve-guard/README.md
new file mode 100644
index 00000000..885235bd
--- /dev/null
+++ b/revenue-refund-liability-reserve-guard/README.md
@@ -0,0 +1,49 @@
+# Revenue Refund Liability Reserve Guard
+
+This module adds a focused refund liability reserve guard for the Revenue
+Infrastructure bounty. It evaluates whether subscription, institutional
+invoice, and compute-credit refund requests are financially safe before cash
+leaves the platform or entitlements are reversed.
+
+## Scope
+
+The guard checks synthetic refund packets for:
+
+- annual subscription refund eligibility and proration
+- institutional invoice reversal evidence and approval status
+- unused AI compute credit refund treatment
+- non-refundable processor fees and tax handling
+- entitlement rollback readiness before cash release
+- refund evidence completeness
+- reserve sufficiency by revenue stream and currency
+- reviewer actions for hold, approve, or manual finance review decisions
+
+This is not a generic quote approval, billing readiness, payment rail,
+dunning, dispute, tax exemption, proration, prepaid credit breakage, or
+collections module. It focuses specifically on refund liability exposure and
+reserve sufficiency before money is returned.
+
+## Issue #20 Requirement Mapping
+
+- Tiered subscription billing: evaluates subscription refunds, annual-plan
+ proration, cancellation terms, and entitlement rollback readiness.
+- AI compute billing: checks unused compute-credit refund requests against
+ consumed usage, restricted grants, and available reserve coverage.
+- Institutional invoicing: validates invoice reversal approvals, PO evidence,
+ and tax/processor fee treatment.
+- Revenue operations: emits deterministic finance review packets, reserve
+ impact, audit digests, and curator actions for revenue teams.
+
+## Validation
+
+```bash
+node revenue-refund-liability-reserve-guard/test.js
+node revenue-refund-liability-reserve-guard/demo.js
+node revenue-refund-liability-reserve-guard/render-video.js
+node --check revenue-refund-liability-reserve-guard/index.js
+node --check revenue-refund-liability-reserve-guard/sample-data.js
+node --check revenue-refund-liability-reserve-guard/test.js
+node --check revenue-refund-liability-reserve-guard/demo.js
+node --check revenue-refund-liability-reserve-guard/render-video.js
+git diff --check
+```
diff --git a/revenue-refund-liability-reserve-guard/demo.js b/revenue-refund-liability-reserve-guard/demo.js
new file mode 100644
index 00000000..ecde0e08
--- /dev/null
+++ b/revenue-refund-liability-reserve-guard/demo.js
@@ -0,0 +1,27 @@
+const fs = require("fs");
+const path = require("path");
+const packets = require("./sample-data");
+const {
+ evaluateRefundBatch,
+ renderMarkdownReport,
+ renderSvgReport,
+} = require("./index");
+
+const reportDir = path.join(__dirname, "reports");
+fs.mkdirSync(reportDir, { recursive: true });
+
+const report = evaluateRefundBatch(packets);
+const jsonPath = path.join(reportDir, "refund-liability-review.json");
+const markdownPath = path.join(reportDir, "refund-liability-review.md");
+const svgPath = path.join(reportDir, "refund-liability-review.svg");
+
+fs.writeFileSync(jsonPath, `${JSON.stringify(report, null, 2)}\n`);
+fs.writeFileSync(markdownPath, renderMarkdownReport(report));
+fs.writeFileSync(svgPath, renderSvgReport(report));
+
+console.log(`wrote ${path.relative(process.cwd(), jsonPath)}`);
+console.log(`wrote ${path.relative(process.cwd(), markdownPath)}`);
+console.log(`wrote ${path.relative(process.cwd(), svgPath)}`);
+console.log(
+ `summary: ${report.summary.hold} held refunds, ${report.summary.refundableAmount} refundable`
+);
diff --git a/revenue-refund-liability-reserve-guard/index.js b/revenue-refund-liability-reserve-guard/index.js
new file mode 100644
index 00000000..1efce99a
--- /dev/null
+++ b/revenue-refund-liability-reserve-guard/index.js
@@ -0,0 +1,269 @@
+const crypto = require("crypto");
+
+function money(value) {
+ return Math.round(Number(value || 0) * 100) / 100;
+}
+
+function requiredEvidenceFor(packet) {
+ const evidence = ["contract-term", "entitlement-rollback"];
+ if (packet.revenueStream === "institutional_invoice") {
+ evidence.push("finance-approval");
+ if (packet.requiresPOEvidence) {
+ evidence.push("po-or-invoice-evidence");
+ }
+ }
+ if (packet.revenueStream === "ai_compute") {
+ evidence.push("credit-ledger", "usage-meter-snapshot");
+ if (packet.restrictedGrantFunded) {
+ evidence.push("grant-refund-approval");
+ }
+ }
+ if (packet.taxCollected > 0) {
+ evidence.push("tax-refund-treatment");
+ }
+ return evidence;
+}
+
+function missingEvidence(packet) {
+ const present = new Set(packet.evidence || []);
+ return requiredEvidenceFor(packet).filter((item) => !present.has(item));
+}
+
+function refundableAmount(packet) {
+ const serviceRefund = Math.min(packet.requestedRefund, packet.unusedServiceValue);
+ const taxComponent = packet.taxRefundable ? packet.taxCollected : 0;
+ return money(Math.max(0, serviceRefund + taxComponent - packet.processorFee));
+}
+
+function reserveImpact(packet) {
+ return money(refundableAmount(packet));
+}
+
+function evaluateRefund(packet) {
+ const blockers = [];
+ const warnings = [];
+ const actions = [];
+ const missing = missingEvidence(packet);
+ const refundable = refundableAmount(packet);
+ const overRequest = money(Math.max(0, packet.requestedRefund - packet.unusedServiceValue));
+ const reserveAfter = money(packet.reserveAvailable - reserveImpact(packet));
+
+ if (missing.length > 0) {
+ blockers.push(`missing evidence: ${missing.join(", ")}`);
+ actions.push("hold refund until evidence packet is complete");
+ }
+
+ if (packet.approvalStatus !== "approved") {
+ blockers.push(`approval status is ${packet.approvalStatus}`);
+ actions.push("route to finance approver before cash release");
+ }
+
+ if (!packet.entitlementsRolledBack) {
+ blockers.push("entitlements are not rolled back");
+ actions.push("rollback entitlements before refund release");
+ }
+
+ if (overRequest > 0) {
+ warnings.push(`requested refund exceeds unused service value by ${overRequest} ${packet.currency}`);
+ actions.push("cap refundable service amount at unused value");
+ }
+
+ if (packet.processorFee > 0) {
+ warnings.push(`processor fee ${packet.processorFee} ${packet.currency} is non-refundable`);
+ }
+
+ if (packet.taxCollected > 0 && !packet.taxRefundable) {
+ warnings.push(`tax collected ${packet.taxCollected} ${packet.currency} requires non-refund treatment`);
+ }
+
+ if (packet.restrictedGrantFunded && !(packet.evidence || []).includes("grant-refund-approval")) {
+ blockers.push("restricted grant-funded compute lacks refund approval");
+ actions.push("hold refund for grant compliance review");
+ }
+
+ if (reserveAfter < 0) {
+ blockers.push(`refund reserve short by ${money(Math.abs(reserveAfter))} ${packet.currency}`);
+ actions.push("top up refund reserve before release");
+ } else if (reserveAfter < refundable * 0.25) {
+ warnings.push(`reserve after refund is low at ${reserveAfter} ${packet.currency}`);
+ actions.push("notify finance about low reserve coverage");
+ }
+
+ if (blockers.length === 0) {
+ actions.push("approve refund for payment queue");
+ actions.push("record reserve draw and revenue reversal");
+ }
+
+ const decision =
+ blockers.length > 0 ? "hold" : warnings.length > 0 ? "approve_with_adjustments" : "approve";
+
+ const result = {
+ refundId: packet.id,
+ customer: packet.customer,
+ revenueStream: packet.revenueStream,
+ currency: packet.currency,
+ requestedRefund: money(packet.requestedRefund),
+ refundableAmount: refundable,
+ reserveAvailable: money(packet.reserveAvailable),
+ reserveAfter,
+ decision,
+ blockers,
+ warnings,
+ actions: [...new Set(actions)],
+ };
+
+ return {
+ ...result,
+ auditDigest: digest(result),
+ };
+}
+
+function evaluateRefundBatch(packets) {
+ const decisions = packets.map(evaluateRefund);
+ const summary = decisions.reduce(
+ (acc, item) => {
+ acc.total += 1;
+ acc[item.decision] += 1;
+ acc.requestedRefund = money(acc.requestedRefund + item.requestedRefund);
+ acc.refundableAmount = money(acc.refundableAmount + item.refundableAmount);
+ acc.reserveDraw = money(acc.reserveDraw + Math.max(0, item.refundableAmount));
+ if (item.decision === "hold") {
+ acc.heldRefunds = money(acc.heldRefunds + item.requestedRefund);
+ }
+ return acc;
+ },
+ {
+ total: 0,
+ approve: 0,
+ approve_with_adjustments: 0,
+ hold: 0,
+ requestedRefund: 0,
+ refundableAmount: 0,
+ reserveDraw: 0,
+ heldRefunds: 0,
+ }
+ );
+
+ const report = {
+ generatedAt: new Date("2026-05-31T00:00:00Z").toISOString(),
+ summary,
+ decisions,
+ };
+
+ return {
+ ...report,
+ auditDigest: digest(report),
+ };
+}
+
+function renderMarkdownReport(report) {
+ const lines = [
+ "# Revenue Refund Liability Reserve Report",
+ "",
+ `Generated: ${report.generatedAt}`,
+ `Audit digest: ${report.auditDigest}`,
+ "",
+ "## Summary",
+ "",
+ `- Refund packets: ${report.summary.total}`,
+ `- Approve: ${report.summary.approve}`,
+ `- Approve with adjustments: ${report.summary.approve_with_adjustments}`,
+ `- Hold: ${report.summary.hold}`,
+ `- Requested refund: ${report.summary.requestedRefund}`,
+ `- Refundable amount: ${report.summary.refundableAmount}`,
+ `- Reserve draw: ${report.summary.reserveDraw}`,
+ `- Held refunds: ${report.summary.heldRefunds}`,
+ "",
+ "## Decisions",
+ "",
+ ];
+
+ for (const item of report.decisions) {
+ lines.push(`### ${item.refundId}`);
+ lines.push("");
+ lines.push(`- Customer: ${item.customer}`);
+ lines.push(`- Stream: ${item.revenueStream}`);
+ lines.push(`- Decision: ${item.decision}`);
+ lines.push(`- Requested refund: ${item.requestedRefund} ${item.currency}`);
+ lines.push(`- Refundable amount: ${item.refundableAmount} ${item.currency}`);
+ lines.push(`- Reserve after: ${item.reserveAfter} ${item.currency}`);
+ lines.push(`- Blockers: ${item.blockers.join("; ") || "none"}`);
+ lines.push(`- Warnings: ${item.warnings.join("; ") || "none"}`);
+ lines.push(`- Actions: ${item.actions.join("; ")}`);
+ lines.push(`- Digest: ${item.auditDigest}`);
+ lines.push("");
+ }
+
+ while (lines[lines.length - 1] === "") {
+ lines.pop();
+ }
+
+ return `${lines.join("\n")}\n`;
+}
+
+function escapeXml(value) {
+ return String(value)
+ .replace(/&/g, "&")
+ .replace(//g, ">")
+ .replace(/"/g, """);
+}
+
+function renderSvgReport(report) {
+ const cards = [
+ ["Packets", report.summary.total, "#355c7d"],
+ ["Approved", report.summary.approve, "#2a9d8f"],
+ ["Adjusted", report.summary.approve_with_adjustments, "#e9c46a"],
+ ["Held", report.summary.hold, "#e76f51"],
+ ["Refundable", `$${report.summary.refundableAmount}`, "#457b9d"],
+ ["Held value", `$${report.summary.heldRefunds}`, "#b56576"],
+ ];
+
+ const cardSvg = cards
+ .map(([label, value, color], index) => {
+ const x = 72 + (index % 3) * 380;
+ const y = 188 + Math.floor(index / 3) * 150;
+ return [
+ ``,
+ ``,
+ `${escapeXml(label)}`,
+ `${escapeXml(value)}`,
+ ].join("\n");
+ })
+ .join("\n");
+
+ const queueRows = report.decisions
+ .filter((item) => item.decision !== "approve")
+ .slice(0, 3)
+ .map((item, index) => {
+ const y = 545 + index * 32;
+ return `${escapeXml(item.refundId)} - ${escapeXml(item.decision)} - ${escapeXml(item.actions[0])}`;
+ })
+ .join("\n");
+
+ return ``;
+}
+
+function digest(value) {
+ return crypto.createHash("sha256").update(JSON.stringify(value)).digest("hex");
+}
+
+module.exports = {
+ evaluateRefund,
+ evaluateRefundBatch,
+ refundableAmount,
+ reserveImpact,
+ requiredEvidenceFor,
+ renderMarkdownReport,
+ renderSvgReport,
+ digest,
+};
diff --git a/revenue-refund-liability-reserve-guard/render-video.js b/revenue-refund-liability-reserve-guard/render-video.js
new file mode 100644
index 00000000..40b0a9df
--- /dev/null
+++ b/revenue-refund-liability-reserve-guard/render-video.js
@@ -0,0 +1,50 @@
+const fs = require("fs");
+const path = require("path");
+const { execFileSync } = require("child_process");
+
+const reportDir = path.join(__dirname, "reports");
+const jsonPath = path.join(reportDir, "refund-liability-review.json");
+const svgPath = path.join(reportDir, "refund-liability-review.svg");
+const framePath = path.join(reportDir, "refund-liability-review.png");
+const outputPath = path.join(reportDir, "demo.mp4");
+
+if (!fs.existsSync(jsonPath) || !fs.existsSync(svgPath)) {
+ require("./demo");
+}
+
+const report = JSON.parse(fs.readFileSync(jsonPath, "utf8"));
+
+execFileSync(
+ "rsvg-convert",
+ ["--width", "1280", "--height", "720", "--output", framePath, svgPath],
+ { stdio: "inherit" }
+);
+
+execFileSync(
+ "ffmpeg",
+ [
+ "-y",
+ "-loop",
+ "1",
+ "-framerate",
+ "25",
+ "-i",
+ framePath,
+ "-t",
+ "4",
+ "-pix_fmt",
+ "yuv420p",
+ "-vf",
+ "scale=1280:720",
+ "-movflags",
+ "+faststart",
+ outputPath,
+ ],
+ { stdio: "inherit" }
+);
+
+fs.unlinkSync(framePath);
+
+console.log(
+ `wrote ${path.relative(process.cwd(), outputPath)} for ${report.summary.total} refund packets`
+);
diff --git a/revenue-refund-liability-reserve-guard/reports/demo.mp4 b/revenue-refund-liability-reserve-guard/reports/demo.mp4
new file mode 100644
index 00000000..879da938
Binary files /dev/null and b/revenue-refund-liability-reserve-guard/reports/demo.mp4 differ
diff --git a/revenue-refund-liability-reserve-guard/reports/refund-liability-review.json b/revenue-refund-liability-reserve-guard/reports/refund-liability-review.json
new file mode 100644
index 00000000..c7d896ce
--- /dev/null
+++ b/revenue-refund-liability-reserve-guard/reports/refund-liability-review.json
@@ -0,0 +1,109 @@
+{
+ "generatedAt": "2026-05-31T00:00:00.000Z",
+ "summary": {
+ "total": 4,
+ "approve": 0,
+ "approve_with_adjustments": 2,
+ "hold": 2,
+ "requestedRefund": 11100,
+ "refundableAmount": 9498,
+ "reserveDraw": 9498,
+ "heldRefunds": 7600
+ },
+ "decisions": [
+ {
+ "refundId": "refund:annual-pro-cancel-clean",
+ "customer": "Northbridge Lab",
+ "revenueStream": "subscription",
+ "currency": "USD",
+ "requestedRefund": 900,
+ "refundableAmount": 938,
+ "reserveAvailable": 1200,
+ "reserveAfter": 262,
+ "decision": "approve_with_adjustments",
+ "blockers": [],
+ "warnings": [
+ "processor fee 34 USD is non-refundable"
+ ],
+ "actions": [
+ "approve refund for payment queue",
+ "record reserve draw and revenue reversal"
+ ],
+ "auditDigest": "39dbf4dd893713fc64b55d5d5ef6de4669a088120c01b5074ec3ed851de81155"
+ },
+ {
+ "refundId": "refund:institutional-invoice-missing-po",
+ "customer": "East Valley University",
+ "revenueStream": "institutional_invoice",
+ "currency": "USD",
+ "requestedRefund": 7000,
+ "refundableAmount": 6200,
+ "reserveAvailable": 5000,
+ "reserveAfter": -1200,
+ "decision": "hold",
+ "blockers": [
+ "missing evidence: entitlement-rollback, finance-approval, po-or-invoice-evidence",
+ "approval status is pending",
+ "entitlements are not rolled back",
+ "refund reserve short by 1200 USD"
+ ],
+ "warnings": [
+ "requested refund exceeds unused service value by 800 USD"
+ ],
+ "actions": [
+ "hold refund until evidence packet is complete",
+ "route to finance approver before cash release",
+ "rollback entitlements before refund release",
+ "cap refundable service amount at unused value",
+ "top up refund reserve before release"
+ ],
+ "auditDigest": "7705b511278e5888ce93be1264ae43da26dd26d8c441be69fbef7d6e50c351d1"
+ },
+ {
+ "refundId": "refund:compute-credit-over-request",
+ "customer": "Helix AI Core",
+ "revenueStream": "ai_compute",
+ "currency": "USD",
+ "requestedRefund": 2600,
+ "refundableAmount": 1815,
+ "reserveAvailable": 2400,
+ "reserveAfter": 585,
+ "decision": "approve_with_adjustments",
+ "blockers": [],
+ "warnings": [
+ "requested refund exceeds unused service value by 700 USD",
+ "processor fee 85 USD is non-refundable"
+ ],
+ "actions": [
+ "cap refundable service amount at unused value",
+ "approve refund for payment queue",
+ "record reserve draw and revenue reversal"
+ ],
+ "auditDigest": "f8c6f3e25b3e20063c04d6ff23ce2c88a6a2975967f406763ccd19049e41aa49"
+ },
+ {
+ "refundId": "refund:restricted-grant-compute",
+ "customer": "Civic Bio Grant",
+ "revenueStream": "ai_compute",
+ "currency": "USD",
+ "requestedRefund": 600,
+ "refundableAmount": 545,
+ "reserveAvailable": 1000,
+ "reserveAfter": 455,
+ "decision": "hold",
+ "blockers": [
+ "missing evidence: grant-refund-approval",
+ "restricted grant-funded compute lacks refund approval"
+ ],
+ "warnings": [
+ "processor fee 55 USD is non-refundable"
+ ],
+ "actions": [
+ "hold refund until evidence packet is complete",
+ "hold refund for grant compliance review"
+ ],
+ "auditDigest": "adda46759e57cf7997019364e7c8467be44025f767d05debfcf68a612fa8798d"
+ }
+ ],
+ "auditDigest": "6c51ddbe808a1a8cacb81a52cfd1e832b78f82bbfc31c0e4df4bb16a373022af"
+}
diff --git a/revenue-refund-liability-reserve-guard/reports/refund-liability-review.md b/revenue-refund-liability-reserve-guard/reports/refund-liability-review.md
new file mode 100644
index 00000000..2255e9de
--- /dev/null
+++ b/revenue-refund-liability-reserve-guard/reports/refund-liability-review.md
@@ -0,0 +1,69 @@
+# Revenue Refund Liability Reserve Report
+
+Generated: 2026-05-31T00:00:00.000Z
+Audit digest: 6c51ddbe808a1a8cacb81a52cfd1e832b78f82bbfc31c0e4df4bb16a373022af
+
+## Summary
+
+- Refund packets: 4
+- Approve: 0
+- Approve with adjustments: 2
+- Hold: 2
+- Requested refund: 11100
+- Refundable amount: 9498
+- Reserve draw: 9498
+- Held refunds: 7600
+
+## Decisions
+
+### refund:annual-pro-cancel-clean
+
+- Customer: Northbridge Lab
+- Stream: subscription
+- Decision: approve_with_adjustments
+- Requested refund: 900 USD
+- Refundable amount: 938 USD
+- Reserve after: 262 USD
+- Blockers: none
+- Warnings: processor fee 34 USD is non-refundable
+- Actions: approve refund for payment queue; record reserve draw and revenue reversal
+- Digest: 39dbf4dd893713fc64b55d5d5ef6de4669a088120c01b5074ec3ed851de81155
+
+### refund:institutional-invoice-missing-po
+
+- Customer: East Valley University
+- Stream: institutional_invoice
+- Decision: hold
+- Requested refund: 7000 USD
+- Refundable amount: 6200 USD
+- Reserve after: -1200 USD
+- Blockers: missing evidence: entitlement-rollback, finance-approval, po-or-invoice-evidence; approval status is pending; entitlements are not rolled back; refund reserve short by 1200 USD
+- Warnings: requested refund exceeds unused service value by 800 USD
+- Actions: hold refund until evidence packet is complete; route to finance approver before cash release; rollback entitlements before refund release; cap refundable service amount at unused value; top up refund reserve before release
+- Digest: 7705b511278e5888ce93be1264ae43da26dd26d8c441be69fbef7d6e50c351d1
+
+### refund:compute-credit-over-request
+
+- Customer: Helix AI Core
+- Stream: ai_compute
+- Decision: approve_with_adjustments
+- Requested refund: 2600 USD
+- Refundable amount: 1815 USD
+- Reserve after: 585 USD
+- Blockers: none
+- Warnings: requested refund exceeds unused service value by 700 USD; processor fee 85 USD is non-refundable
+- Actions: cap refundable service amount at unused value; approve refund for payment queue; record reserve draw and revenue reversal
+- Digest: f8c6f3e25b3e20063c04d6ff23ce2c88a6a2975967f406763ccd19049e41aa49
+
+### refund:restricted-grant-compute
+
+- Customer: Civic Bio Grant
+- Stream: ai_compute
+- Decision: hold
+- Requested refund: 600 USD
+- Refundable amount: 545 USD
+- Reserve after: 455 USD
+- Blockers: missing evidence: grant-refund-approval; restricted grant-funded compute lacks refund approval
+- Warnings: processor fee 55 USD is non-refundable
+- Actions: hold refund until evidence packet is complete; hold refund for grant compliance review
+- Digest: adda46759e57cf7997019364e7c8467be44025f767d05debfcf68a612fa8798d
diff --git a/revenue-refund-liability-reserve-guard/reports/refund-liability-review.svg b/revenue-refund-liability-reserve-guard/reports/refund-liability-review.svg
new file mode 100644
index 00000000..baba5bb9
--- /dev/null
+++ b/revenue-refund-liability-reserve-guard/reports/refund-liability-review.svg
@@ -0,0 +1,35 @@
+
\ No newline at end of file
diff --git a/revenue-refund-liability-reserve-guard/sample-data.js b/revenue-refund-liability-reserve-guard/sample-data.js
new file mode 100644
index 00000000..3bb58bee
--- /dev/null
+++ b/revenue-refund-liability-reserve-guard/sample-data.js
@@ -0,0 +1,75 @@
+const refundPackets = [
+ {
+ id: "refund:annual-pro-cancel-clean",
+ customer: "Northbridge Lab",
+ revenueStream: "subscription",
+ currency: "USD",
+ originalCharge: 2400,
+ requestedRefund: 900,
+ unusedServiceValue: 960,
+ processorFee: 34,
+ taxCollected: 72,
+ taxRefundable: true,
+ cancellationReason: "lab merged into institutional plan",
+ approvalStatus: "approved",
+ evidence: ["contract-term", "cancellation-ticket", "entitlement-rollback", "tax-refund-treatment"],
+ entitlementsRolledBack: true,
+ reserveAvailable: 1200,
+ },
+ {
+ id: "refund:institutional-invoice-missing-po",
+ customer: "East Valley University",
+ revenueStream: "institutional_invoice",
+ currency: "USD",
+ originalCharge: 18000,
+ requestedRefund: 7000,
+ unusedServiceValue: 6200,
+ processorFee: 0,
+ taxCollected: 0,
+ taxRefundable: false,
+ cancellationReason: "department budget reallocation",
+ approvalStatus: "pending",
+ evidence: ["contract-term"],
+ entitlementsRolledBack: false,
+ reserveAvailable: 5000,
+ requiresPOEvidence: true,
+ },
+ {
+ id: "refund:compute-credit-over-request",
+ customer: "Helix AI Core",
+ revenueStream: "ai_compute",
+ currency: "USD",
+ originalCharge: 5000,
+ requestedRefund: 2600,
+ unusedServiceValue: 1900,
+ processorFee: 85,
+ taxCollected: 0,
+ taxRefundable: false,
+ cancellationReason: "unused top-up credits",
+ approvalStatus: "approved",
+ evidence: ["contract-term", "credit-ledger", "usage-meter-snapshot", "entitlement-rollback"],
+ entitlementsRolledBack: true,
+ reserveAvailable: 2400,
+ restrictedGrantFunded: false,
+ },
+ {
+ id: "refund:restricted-grant-compute",
+ customer: "Civic Bio Grant",
+ revenueStream: "ai_compute",
+ currency: "USD",
+ originalCharge: 3200,
+ requestedRefund: 600,
+ unusedServiceValue: 650,
+ processorFee: 55,
+ taxCollected: 0,
+ taxRefundable: false,
+ cancellationReason: "grant-funded compute pack closed",
+ approvalStatus: "approved",
+ evidence: ["contract-term", "entitlement-rollback", "credit-ledger", "usage-meter-snapshot", "grant-restriction"],
+ entitlementsRolledBack: true,
+ reserveAvailable: 1000,
+ restrictedGrantFunded: true,
+ },
+];
+
+module.exports = refundPackets;
diff --git a/revenue-refund-liability-reserve-guard/test.js b/revenue-refund-liability-reserve-guard/test.js
new file mode 100644
index 00000000..7c6a05bb
--- /dev/null
+++ b/revenue-refund-liability-reserve-guard/test.js
@@ -0,0 +1,66 @@
+const assert = require("assert");
+const packets = require("./sample-data");
+const {
+ evaluateRefund,
+ evaluateRefundBatch,
+ refundableAmount,
+ requiredEvidenceFor,
+} = require("./index");
+
+const report = evaluateRefundBatch(packets);
+
+function decision(id) {
+ return report.decisions.find((item) => item.refundId === id);
+}
+
+assert.strictEqual(report.summary.total, 4);
+assert.strictEqual(report.summary.approve, 0);
+assert.strictEqual(report.summary.approve_with_adjustments, 2);
+assert.strictEqual(report.summary.hold, 2);
+assert.strictEqual(report.summary.requestedRefund, 11100);
+assert.strictEqual(report.summary.heldRefunds, 7600);
+
+const clean = decision("refund:annual-pro-cancel-clean");
+assert.strictEqual(clean.decision, "approve_with_adjustments");
+assert.strictEqual(clean.refundableAmount, 938);
+assert(
+ clean.warnings.some((item) => item.includes("processor fee")),
+ "clean annual refund should surface non-refundable processor fee"
+);
+
+const invoice = decision("refund:institutional-invoice-missing-po");
+assert.strictEqual(invoice.decision, "hold");
+assert(
+ invoice.blockers.some((item) => item.includes("po-or-invoice-evidence")),
+ "institutional refund should require PO or invoice evidence"
+);
+assert(
+ invoice.blockers.some((item) => item.includes("approval status")),
+ "institutional refund should require approved finance status"
+);
+assert(
+ invoice.blockers.some((item) => item.includes("reserve short")),
+ "institutional refund should be held if reserve coverage is short"
+);
+
+const compute = decision("refund:compute-credit-over-request");
+assert.strictEqual(compute.decision, "approve_with_adjustments");
+assert.strictEqual(compute.refundableAmount, 1815);
+assert(
+ compute.warnings.some((item) => item.includes("exceeds unused service value")),
+ "compute over-request should be capped by unused service value"
+);
+
+const restricted = decision("refund:restricted-grant-compute");
+assert.strictEqual(restricted.decision, "hold");
+assert(
+ restricted.blockers.some((item) => item.includes("grant-refund-approval")),
+ "restricted grant funded compute refund needs grant approval evidence"
+);
+
+assert.strictEqual(refundableAmount(packets[0]), 938);
+assert(requiredEvidenceFor(packets[1]).includes("po-or-invoice-evidence"));
+assert.match(report.auditDigest, /^[a-f0-9]{64}$/);
+assert.match(evaluateRefund(packets[0]).auditDigest, /^[a-f0-9]{64}$/);
+
+console.log("revenue refund liability reserve guard tests passed");