diff --git a/grandfathered-pricing-migration-guard/README.md b/grandfathered-pricing-migration-guard/README.md
new file mode 100644
index 00000000..485ab04f
--- /dev/null
+++ b/grandfathered-pricing-migration-guard/README.md
@@ -0,0 +1,36 @@
+# Grandfathered Pricing Migration Guard
+
+This self-contained Revenue Infrastructure slice validates whether a legacy-price customer can be migrated to a new price book before an invoice is released.
+
+The guard focuses on a gap that is separate from existing billing ledgers, usage metering, quota rollover, coupon checks, committed-usage true-ups, grant billing, FX settlement, tax handling, invoice delivery, and analytics-seat licensing work.
+
+## What it checks
+
+- active price locks and grandfathered contract windows
+- required customer notice periods before a price increase
+- explicit consent for material increases
+- annual and volume discount compatibility
+- included seat and compute regressions
+- add-on parity during plan migration
+- currency changes that need finance approval
+- dispute, delinquency, and strategic-account review holds
+- reviewer evidence such as order forms, runbooks, legal approval, and sales owner signoff
+
+## Run locally
+
+```sh
+npm test
+npm run demo
+```
+
+The demo writes reviewer artifacts under `artifacts/`:
+
+- `pricing-migration-results.json`
+- `pricing-migration-report.md`
+- `pricing-migration-summary.svg`
+- `pricing-migration-demo.mp4` when `swift scripts/make-demo-video.swift artifacts/pricing-migration-demo.mp4` is run on macOS
+- `demo-transcript.md`
+
+## Boundaries
+
+All data is synthetic. The module does not call payment processors, customer systems, pricing APIs, bank rails, external services, or private billing records.
diff --git a/grandfathered-pricing-migration-guard/REQUIREMENT_MAP.md b/grandfathered-pricing-migration-guard/REQUIREMENT_MAP.md
new file mode 100644
index 00000000..868d5f2a
--- /dev/null
+++ b/grandfathered-pricing-migration-guard/REQUIREMENT_MAP.md
@@ -0,0 +1,17 @@
+# Requirement Map
+
+| Issue requirement | Implementation |
+| --- | --- |
+| Tiered subscription billing | Evaluates current and target plans, price books, billing cycles, included seats, and included compute. |
+| Volume discounts and annual cycles | Blocks or reviews incompatible annual commitments, volume discounts, seat caps, and billing-cycle regressions. |
+| Free trials, coupons, consortium pricing coexistence | Keeps this slice distinct by checking plan migration release readiness rather than coupon eligibility itself. |
+| Institutional invoicing | Produces deterministic RELEASE, REVIEW, or HOLD decisions before legacy-price invoices can be released. |
+| Usage-based pricing for AI compute | Detects target plan compute regressions against recent average compute usage. |
+| Transparent quotas and usage meters | Includes included-seat and compute-hour deltas in every reviewer result. |
+| High-margin, predictable recurring revenue | Prevents surprise migrations, missing consent, active price-lock violations, and add-on parity regressions before invoices ship. |
+| Reviewer-ready evidence | Demo script generates JSON, Markdown, SVG, and transcript artifacts from synthetic account packets. |
+| Safe contribution boundary | No payment processor calls, no live customers, no credentials, no external APIs, and no private billing records. |
+
+## Distinct slice statement
+
+This contribution is focused only on grandfathered-price and price-book migration release decisions. It intentionally does not implement generic subscriptions, payment collection, quota rollover, tax/VAT, dunning, FX settlement, grant compute budgets, committed-usage true-ups, analytics-seat licensing, invoice delivery, or coupon eligibility.
diff --git a/grandfathered-pricing-migration-guard/artifacts/demo-transcript.md b/grandfathered-pricing-migration-guard/artifacts/demo-transcript.md
new file mode 100644
index 00000000..f234d2c1
--- /dev/null
+++ b/grandfathered-pricing-migration-guard/artifacts/demo-transcript.md
@@ -0,0 +1,14 @@
+# Demo Transcript
+
+1. Load five synthetic legacy-price account packets.
+2. Evaluate notice windows, price locks, consent thresholds, add-on parity, usage regressions, and reviewer evidence.
+3. Emit deterministic invoice-release decisions: RELEASE_INVOICE, REVIEW_BEFORE_RELEASE, or HOLD_INVOICE.
+4. Write JSON, Markdown, and SVG artifacts for reviewer replay.
+
+## Demo Output
+
+- Release: 1
+- Review: 1
+- Hold: 3
+- Held accounts: acct-lab-price-lock, acct-notice-gap, acct-currency-mismatch
+- Review accounts: acct-grace-review
diff --git a/grandfathered-pricing-migration-guard/artifacts/pricing-migration-demo.mp4 b/grandfathered-pricing-migration-guard/artifacts/pricing-migration-demo.mp4
new file mode 100644
index 00000000..135e04a6
Binary files /dev/null and b/grandfathered-pricing-migration-guard/artifacts/pricing-migration-demo.mp4 differ
diff --git a/grandfathered-pricing-migration-guard/artifacts/pricing-migration-report.md b/grandfathered-pricing-migration-guard/artifacts/pricing-migration-report.md
new file mode 100644
index 00000000..3934d672
--- /dev/null
+++ b/grandfathered-pricing-migration-guard/artifacts/pricing-migration-report.md
@@ -0,0 +1,51 @@
+# Grandfathered Pricing Migration Report
+
+As of: 2026-06-18
+
+## Summary
+
+- Total accounts: 5
+- Release: 1
+- Review: 1
+- Hold: 3
+- Monthly revenue delta: USD 1,510.00 before currency-specific settlement review
+
+## Account Decisions
+
+| Account | Decision | Monthly delta | Primary reason |
+| --- | --- | ---: | --- |
+| Northbridge University Research Office | RELEASE_INVOICE | USD 390.00 | All release checks passed |
+| Meridian Lab Collaborative | HOLD_INVOICE | USD 160.00 | ACTIVE_PRICE_LOCK: Price lock remains active until 2026-12-31. |
+| Civic Methods Consortium | HOLD_INVOICE | USD 520.00 | ADD_ON_PARITY_GAP: Target plan is missing legacy add-ons: sso. |
+| Helix Bioinformatics Studio | REVIEW_BEFORE_RELEASE | EUR 180.00 | INSIDE_LEGACY_GRACE_PERIOD: Grandfathering ended 29 days ago, inside the 45-day review grace period. |
+| Pacific Applied Science Center | HOLD_INVOICE | HKD 260.00 | CURRENCY_CHANGE_WITHOUT_FINANCE_APPROVAL: Currency changes from USD to HKD. |
+
+## Release Actions
+
+### Northbridge University Research Office
+- Release invoice under the target price book.
+
+### Meridian Lab Collaborative
+- Keep the account on the legacy price book until the price lock expires or a new order form is signed.
+- Request explicit customer approval before creating a new-price invoice.
+- Hold invoice release until the notice window is satisfied.
+- Collect explicit consent or signed renewal before migration.
+- Attach legal approval before release.
+
+### Civic Methods Consortium
+- Route through renewals review before release.
+- Hold invoice release until the notice window is satisfied.
+- Collect explicit consent or signed renewal before migration.
+- Attach legal approval before release.
+- Increase target seat allowance or collect admin confirmation for seat reduction.
+- Confirm customer-facing compute entitlement changes before billing.
+- Add equivalent add-ons or document an accepted replacement.
+- Confirm volume discount recalculation with revenue operations.
+- Attach named sales owner or renewal owner signoff.
+
+### Helix Bioinformatics Studio
+- Route through renewals review before release.
+- Attach legal approval before release.
+
+### Pacific Applied Science Center
+- Add finance approval and settlement notes for the currency migration.
diff --git a/grandfathered-pricing-migration-guard/artifacts/pricing-migration-results.json b/grandfathered-pricing-migration-guard/artifacts/pricing-migration-results.json
new file mode 100644
index 00000000..9972fee7
--- /dev/null
+++ b/grandfathered-pricing-migration-guard/artifacts/pricing-migration-results.json
@@ -0,0 +1,326 @@
+{
+ "asOf": "2026-06-18",
+ "policy": {
+ "noticeDays": 90,
+ "legacyGraceDays": 45,
+ "materialIncreasePercent": 15,
+ "maxComputeRegressionPercent": 20,
+ "defaultConsentThresholdPercent": 12
+ },
+ "summary": {
+ "totalAccounts": 5,
+ "release": 1,
+ "review": 1,
+ "hold": 3,
+ "totalMonthlyDeltaCents": 151000,
+ "heldAccountIds": [
+ "acct-lab-price-lock",
+ "acct-notice-gap",
+ "acct-currency-mismatch"
+ ],
+ "reviewAccountIds": [
+ "acct-grace-review"
+ ],
+ "topRisks": [
+ {
+ "accountId": "acct-lab-price-lock",
+ "severity": "HOLD_INVOICE",
+ "code": "ACTIVE_PRICE_LOCK"
+ },
+ {
+ "accountId": "acct-lab-price-lock",
+ "severity": "HOLD_INVOICE",
+ "code": "GRANDFATHERING_BLOCKS_AUTO_MIGRATION"
+ },
+ {
+ "accountId": "acct-lab-price-lock",
+ "severity": "HOLD_INVOICE",
+ "code": "MISSING_MATERIAL_INCREASE_CONSENT"
+ },
+ {
+ "accountId": "acct-lab-price-lock",
+ "severity": "HOLD_INVOICE",
+ "code": "NOTICE_WINDOW_NOT_MET"
+ },
+ {
+ "accountId": "acct-notice-gap",
+ "severity": "HOLD_INVOICE",
+ "code": "ADD_ON_PARITY_GAP"
+ },
+ {
+ "accountId": "acct-notice-gap",
+ "severity": "HOLD_INVOICE",
+ "code": "MISSING_MATERIAL_INCREASE_CONSENT"
+ },
+ {
+ "accountId": "acct-notice-gap",
+ "severity": "HOLD_INVOICE",
+ "code": "NOTICE_WINDOW_NOT_MET"
+ },
+ {
+ "accountId": "acct-notice-gap",
+ "severity": "HOLD_INVOICE",
+ "code": "SEAT_CAP_REGRESSION"
+ }
+ ]
+ },
+ "results": [
+ {
+ "accountId": "acct-univ-ready",
+ "accountName": "Northbridge University Research Office",
+ "segment": "institution",
+ "decision": "RELEASE_INVOICE",
+ "priceImpact": {
+ "currentMonthlyCents": 420000,
+ "targetMonthlyCents": 459000,
+ "deltaMonthlyCents": 39000,
+ "increasePercent": 9.29,
+ "displayDelta": "USD 390.00"
+ },
+ "entitlementImpact": {
+ "seatDelta": 20,
+ "computeHourDelta": 200,
+ "missingAddOns": []
+ },
+ "evidence": {
+ "customerNoticeId": "notice-NBU-2026-pricebook",
+ "consentReceivedAt": "2026-04-02",
+ "financeApproval": "fin-2026-104",
+ "legalApproval": "legal-legacy-migration-7",
+ "migrationRunbook": "runbook-pricebook-2026-v3",
+ "salesOwner": "enterprise-renewals"
+ },
+ "reasons": [],
+ "actions": [],
+ "riskScore": 0
+ },
+ {
+ "accountId": "acct-lab-price-lock",
+ "accountName": "Meridian Lab Collaborative",
+ "segment": "lab",
+ "decision": "HOLD_INVOICE",
+ "priceImpact": {
+ "currentMonthlyCents": 78000,
+ "targetMonthlyCents": 94000,
+ "deltaMonthlyCents": 16000,
+ "increasePercent": 20.51,
+ "displayDelta": "USD 160.00"
+ },
+ "entitlementImpact": {
+ "seatDelta": 0,
+ "computeHourDelta": 0,
+ "missingAddOns": []
+ },
+ "evidence": {
+ "customerNoticeId": "notice-MLC-2026-pricebook",
+ "consentReceivedAt": null,
+ "financeApproval": "fin-2026-188",
+ "legalApproval": null,
+ "migrationRunbook": "runbook-pricebook-2026-v3",
+ "salesOwner": "lab-success"
+ },
+ "reasons": [
+ {
+ "severity": "HOLD_INVOICE",
+ "code": "ACTIVE_PRICE_LOCK",
+ "message": "Price lock remains active until 2026-12-31."
+ },
+ {
+ "severity": "HOLD_INVOICE",
+ "code": "GRANDFATHERING_BLOCKS_AUTO_MIGRATION",
+ "message": "Contract is grandfathered until 2026-12-31 and does not allow automatic migration."
+ },
+ {
+ "severity": "HOLD_INVOICE",
+ "code": "MISSING_MATERIAL_INCREASE_CONSENT",
+ "message": "Increase is 20.51% and requires consent above 10%."
+ },
+ {
+ "severity": "HOLD_INVOICE",
+ "code": "NOTICE_WINDOW_NOT_MET",
+ "message": "Customer notice is 29 days old; policy requires 90 days."
+ },
+ {
+ "severity": "REVIEW_BEFORE_RELEASE",
+ "code": "MATERIAL_INCREASE_NEEDS_LEGAL_REVIEW",
+ "message": "Increase is 20.51%, above the 15% materiality threshold."
+ }
+ ],
+ "actions": [
+ "Keep the account on the legacy price book until the price lock expires or a new order form is signed.",
+ "Request explicit customer approval before creating a new-price invoice.",
+ "Hold invoice release until the notice window is satisfied.",
+ "Collect explicit consent or signed renewal before migration.",
+ "Attach legal approval before release."
+ ],
+ "riskScore": 9
+ },
+ {
+ "accountId": "acct-notice-gap",
+ "accountName": "Civic Methods Consortium",
+ "segment": "institution",
+ "decision": "HOLD_INVOICE",
+ "priceImpact": {
+ "currentMonthlyCents": 250000,
+ "targetMonthlyCents": 302000,
+ "deltaMonthlyCents": 52000,
+ "increasePercent": 20.8,
+ "displayDelta": "USD 520.00"
+ },
+ "entitlementImpact": {
+ "seatDelta": -10,
+ "computeHourDelta": -80,
+ "missingAddOns": [
+ "sso"
+ ]
+ },
+ "evidence": {
+ "customerNoticeId": "notice-CMC-2026-pricebook",
+ "consentReceivedAt": null,
+ "financeApproval": null,
+ "legalApproval": null,
+ "migrationRunbook": "runbook-pricebook-2026-v3",
+ "salesOwner": null
+ },
+ "reasons": [
+ {
+ "severity": "HOLD_INVOICE",
+ "code": "ADD_ON_PARITY_GAP",
+ "message": "Target plan is missing legacy add-ons: sso."
+ },
+ {
+ "severity": "HOLD_INVOICE",
+ "code": "MISSING_MATERIAL_INCREASE_CONSENT",
+ "message": "Increase is 20.8% and requires consent above 10%."
+ },
+ {
+ "severity": "HOLD_INVOICE",
+ "code": "NOTICE_WINDOW_NOT_MET",
+ "message": "Customer notice is 21 days old; policy requires 90 days."
+ },
+ {
+ "severity": "HOLD_INVOICE",
+ "code": "SEAT_CAP_REGRESSION",
+ "message": "Target plan includes 170 seats but current usage has 178."
+ },
+ {
+ "severity": "REVIEW_BEFORE_RELEASE",
+ "code": "COMPUTE_ENTITLEMENT_REGRESSION",
+ "message": "Target compute allowance is 520 hours while recent usage is 581."
+ },
+ {
+ "severity": "REVIEW_BEFORE_RELEASE",
+ "code": "INSIDE_LEGACY_GRACE_PERIOD",
+ "message": "Grandfathering ended 19 days ago, inside the 45-day review grace period."
+ },
+ {
+ "severity": "REVIEW_BEFORE_RELEASE",
+ "code": "MATERIAL_INCREASE_NEEDS_LEGAL_REVIEW",
+ "message": "Increase is 20.8%, above the 15% materiality threshold."
+ },
+ {
+ "severity": "REVIEW_BEFORE_RELEASE",
+ "code": "STRATEGIC_ACCOUNT_OWNER_MISSING",
+ "message": "Strategic account lacks a sales-owner signoff."
+ },
+ {
+ "severity": "REVIEW_BEFORE_RELEASE",
+ "code": "VOLUME_DISCOUNT_SEAT_BASE_CHANGED",
+ "message": "Volume discount 11% is tied to 180 legacy seats."
+ }
+ ],
+ "actions": [
+ "Route through renewals review before release.",
+ "Hold invoice release until the notice window is satisfied.",
+ "Collect explicit consent or signed renewal before migration.",
+ "Attach legal approval before release.",
+ "Increase target seat allowance or collect admin confirmation for seat reduction.",
+ "Confirm customer-facing compute entitlement changes before billing.",
+ "Add equivalent add-ons or document an accepted replacement.",
+ "Confirm volume discount recalculation with revenue operations.",
+ "Attach named sales owner or renewal owner signoff."
+ ],
+ "riskScore": 13
+ },
+ {
+ "accountId": "acct-grace-review",
+ "accountName": "Helix Bioinformatics Studio",
+ "segment": "lab",
+ "decision": "REVIEW_BEFORE_RELEASE",
+ "priceImpact": {
+ "currentMonthlyCents": 114000,
+ "targetMonthlyCents": 132000,
+ "deltaMonthlyCents": 18000,
+ "increasePercent": 15.79,
+ "displayDelta": "EUR 180.00"
+ },
+ "entitlementImpact": {
+ "seatDelta": 10,
+ "computeHourDelta": 10,
+ "missingAddOns": []
+ },
+ "evidence": {
+ "customerNoticeId": "notice-HBS-2026-pricebook",
+ "consentReceivedAt": null,
+ "financeApproval": "fin-eu-2026-077",
+ "legalApproval": null,
+ "migrationRunbook": "runbook-pricebook-2026-v3",
+ "salesOwner": "eu-renewals"
+ },
+ "reasons": [
+ {
+ "severity": "REVIEW_BEFORE_RELEASE",
+ "code": "INSIDE_LEGACY_GRACE_PERIOD",
+ "message": "Grandfathering ended 29 days ago, inside the 45-day review grace period."
+ },
+ {
+ "severity": "REVIEW_BEFORE_RELEASE",
+ "code": "MATERIAL_INCREASE_NEEDS_LEGAL_REVIEW",
+ "message": "Increase is 15.79%, above the 15% materiality threshold."
+ }
+ ],
+ "actions": [
+ "Route through renewals review before release.",
+ "Attach legal approval before release."
+ ],
+ "riskScore": 2
+ },
+ {
+ "accountId": "acct-currency-mismatch",
+ "accountName": "Pacific Applied Science Center",
+ "segment": "institution",
+ "decision": "HOLD_INVOICE",
+ "priceImpact": {
+ "currentMonthlyCents": 360000,
+ "targetMonthlyCents": 386000,
+ "deltaMonthlyCents": 26000,
+ "increasePercent": 7.22,
+ "displayDelta": "HKD 260.00"
+ },
+ "entitlementImpact": {
+ "seatDelta": 10,
+ "computeHourDelta": 40,
+ "missingAddOns": []
+ },
+ "evidence": {
+ "customerNoticeId": "notice-PASC-2026-pricebook",
+ "consentReceivedAt": "2026-04-18",
+ "financeApproval": null,
+ "legalApproval": "legal-apac-2026-02",
+ "migrationRunbook": "runbook-pricebook-2026-v3",
+ "salesOwner": "apac-renewals"
+ },
+ "reasons": [
+ {
+ "severity": "HOLD_INVOICE",
+ "code": "CURRENCY_CHANGE_WITHOUT_FINANCE_APPROVAL",
+ "message": "Currency changes from USD to HKD."
+ }
+ ],
+ "actions": [
+ "Add finance approval and settlement notes for the currency migration."
+ ],
+ "riskScore": 2
+ }
+ ]
+}
diff --git a/grandfathered-pricing-migration-guard/artifacts/pricing-migration-summary.svg b/grandfathered-pricing-migration-guard/artifacts/pricing-migration-summary.svg
new file mode 100644
index 00000000..3132a530
--- /dev/null
+++ b/grandfathered-pricing-migration-guard/artifacts/pricing-migration-summary.svg
@@ -0,0 +1,12 @@
+
diff --git a/grandfathered-pricing-migration-guard/examples/migration-packets.json b/grandfathered-pricing-migration-guard/examples/migration-packets.json
new file mode 100644
index 00000000..211f6a82
--- /dev/null
+++ b/grandfathered-pricing-migration-guard/examples/migration-packets.json
@@ -0,0 +1,274 @@
+{
+ "asOf": "2026-06-18",
+ "policy": {
+ "noticeDays": 90,
+ "legacyGraceDays": 45,
+ "materialIncreasePercent": 15,
+ "maxComputeRegressionPercent": 20,
+ "defaultConsentThresholdPercent": 12
+ },
+ "accounts": [
+ {
+ "id": "acct-univ-ready",
+ "name": "Northbridge University Research Office",
+ "segment": "institution",
+ "currency": "USD",
+ "currentPlan": {
+ "planId": "institution-legacy-2024",
+ "priceBookId": "pb-legacy-2024",
+ "monthlyPriceCents": 420000,
+ "billingCycle": "annual",
+ "includedSeats": 240,
+ "includedComputeHours": 900,
+ "addOns": ["analytics-api", "priority-support"]
+ },
+ "targetPlan": {
+ "planId": "institution-enterprise-2026",
+ "priceBookId": "pb-enterprise-2026",
+ "monthlyPriceCents": 459000,
+ "billingCycle": "annual",
+ "includedSeats": 260,
+ "includedComputeHours": 1100,
+ "addOns": ["analytics-api", "priority-support"]
+ },
+ "contract": {
+ "grandfatheredUntil": "2026-04-30",
+ "priceLockUntil": "2026-03-31",
+ "allowsAutoMigration": true,
+ "requiresConsentAbovePct": 12,
+ "noticeSentAt": "2026-02-10",
+ "consentReceivedAt": "2026-04-02",
+ "signedOrderFormAt": "2026-01-18"
+ },
+ "usage": {
+ "activeSeats": 221,
+ "averageComputeHours": 812
+ },
+ "discounts": {
+ "annualCommitment": true,
+ "volumePercent": 8
+ },
+ "evidence": {
+ "customerNoticeId": "notice-NBU-2026-pricebook",
+ "addOnParityReview": "passed",
+ "financeApproval": "fin-2026-104",
+ "legalApproval": "legal-legacy-migration-7",
+ "migrationRunbook": "runbook-pricebook-2026-v3",
+ "salesOwner": "enterprise-renewals"
+ },
+ "flags": {
+ "billingDispute": false,
+ "delinquent": false,
+ "strategicAccount": true
+ }
+ },
+ {
+ "id": "acct-lab-price-lock",
+ "name": "Meridian Lab Collaborative",
+ "segment": "lab",
+ "currency": "USD",
+ "currentPlan": {
+ "planId": "lab-growth-legacy",
+ "priceBookId": "pb-lab-2023",
+ "monthlyPriceCents": 78000,
+ "billingCycle": "monthly",
+ "includedSeats": 45,
+ "includedComputeHours": 180,
+ "addOns": ["private-workspaces"]
+ },
+ "targetPlan": {
+ "planId": "lab-growth-2026",
+ "priceBookId": "pb-lab-2026",
+ "monthlyPriceCents": 94000,
+ "billingCycle": "monthly",
+ "includedSeats": 45,
+ "includedComputeHours": 180,
+ "addOns": ["private-workspaces"]
+ },
+ "contract": {
+ "grandfatheredUntil": "2026-12-31",
+ "priceLockUntil": "2026-12-31",
+ "allowsAutoMigration": false,
+ "requiresConsentAbovePct": 10,
+ "noticeSentAt": "2026-05-20",
+ "signedOrderFormAt": "2025-12-19"
+ },
+ "usage": {
+ "activeSeats": 39,
+ "averageComputeHours": 131
+ },
+ "discounts": {
+ "annualCommitment": false,
+ "volumePercent": 0
+ },
+ "evidence": {
+ "customerNoticeId": "notice-MLC-2026-pricebook",
+ "addOnParityReview": "passed",
+ "financeApproval": "fin-2026-188",
+ "migrationRunbook": "runbook-pricebook-2026-v3",
+ "salesOwner": "lab-success"
+ },
+ "flags": {
+ "billingDispute": false,
+ "delinquent": false,
+ "strategicAccount": false
+ }
+ },
+ {
+ "id": "acct-notice-gap",
+ "name": "Civic Methods Consortium",
+ "segment": "institution",
+ "currency": "USD",
+ "currentPlan": {
+ "planId": "consortium-legacy",
+ "priceBookId": "pb-public-sector-2024",
+ "monthlyPriceCents": 250000,
+ "billingCycle": "annual",
+ "includedSeats": 180,
+ "includedComputeHours": 600,
+ "addOns": ["analytics-api", "sso"]
+ },
+ "targetPlan": {
+ "planId": "consortium-2026",
+ "priceBookId": "pb-public-sector-2026",
+ "monthlyPriceCents": 302000,
+ "billingCycle": "annual",
+ "includedSeats": 170,
+ "includedComputeHours": 520,
+ "addOns": ["analytics-api"]
+ },
+ "contract": {
+ "grandfatheredUntil": "2026-05-30",
+ "priceLockUntil": "2026-05-30",
+ "allowsAutoMigration": true,
+ "requiresConsentAbovePct": 10,
+ "noticeSentAt": "2026-05-28",
+ "signedOrderFormAt": "2025-11-04"
+ },
+ "usage": {
+ "activeSeats": 178,
+ "averageComputeHours": 581
+ },
+ "discounts": {
+ "annualCommitment": true,
+ "volumePercent": 11
+ },
+ "evidence": {
+ "customerNoticeId": "notice-CMC-2026-pricebook",
+ "addOnParityReview": "missing",
+ "migrationRunbook": "runbook-pricebook-2026-v3"
+ },
+ "flags": {
+ "billingDispute": false,
+ "delinquent": false,
+ "strategicAccount": true
+ }
+ },
+ {
+ "id": "acct-grace-review",
+ "name": "Helix Bioinformatics Studio",
+ "segment": "lab",
+ "currency": "EUR",
+ "currentPlan": {
+ "planId": "lab-pro-legacy-eu",
+ "priceBookId": "pb-eu-2024",
+ "monthlyPriceCents": 114000,
+ "billingCycle": "annual",
+ "includedSeats": 70,
+ "includedComputeHours": 350,
+ "addOns": ["priority-support"]
+ },
+ "targetPlan": {
+ "planId": "lab-pro-eu-2026",
+ "priceBookId": "pb-eu-2026",
+ "monthlyPriceCents": 132000,
+ "billingCycle": "annual",
+ "includedSeats": 80,
+ "includedComputeHours": 360,
+ "addOns": ["priority-support"]
+ },
+ "contract": {
+ "grandfatheredUntil": "2026-05-20",
+ "priceLockUntil": "2026-05-20",
+ "allowsAutoMigration": true,
+ "requiresConsentAbovePct": 20,
+ "noticeSentAt": "2026-02-15",
+ "signedOrderFormAt": "2025-10-10"
+ },
+ "usage": {
+ "activeSeats": 61,
+ "averageComputeHours": 342
+ },
+ "discounts": {
+ "annualCommitment": true,
+ "volumePercent": 5
+ },
+ "evidence": {
+ "customerNoticeId": "notice-HBS-2026-pricebook",
+ "addOnParityReview": "passed",
+ "financeApproval": "fin-eu-2026-077",
+ "migrationRunbook": "runbook-pricebook-2026-v3",
+ "salesOwner": "eu-renewals"
+ },
+ "flags": {
+ "billingDispute": false,
+ "delinquent": false,
+ "strategicAccount": false
+ }
+ },
+ {
+ "id": "acct-currency-mismatch",
+ "name": "Pacific Applied Science Center",
+ "segment": "institution",
+ "currency": "USD",
+ "currentPlan": {
+ "planId": "institution-legacy-apac",
+ "priceBookId": "pb-apac-2024",
+ "monthlyPriceCents": 360000,
+ "billingCycle": "annual",
+ "includedSeats": 210,
+ "includedComputeHours": 760,
+ "addOns": ["analytics-api", "priority-support", "sso"]
+ },
+ "targetPlan": {
+ "planId": "institution-apac-2026",
+ "priceBookId": "pb-apac-2026",
+ "monthlyPriceCents": 386000,
+ "billingCycle": "annual",
+ "currency": "HKD",
+ "includedSeats": 220,
+ "includedComputeHours": 800,
+ "addOns": ["analytics-api", "priority-support", "sso"]
+ },
+ "contract": {
+ "grandfatheredUntil": "2026-03-31",
+ "priceLockUntil": "2026-03-31",
+ "allowsAutoMigration": true,
+ "requiresConsentAbovePct": 15,
+ "noticeSentAt": "2026-01-20",
+ "consentReceivedAt": "2026-04-18",
+ "signedOrderFormAt": "2026-01-05"
+ },
+ "usage": {
+ "activeSeats": 201,
+ "averageComputeHours": 721
+ },
+ "discounts": {
+ "annualCommitment": true,
+ "volumePercent": 6
+ },
+ "evidence": {
+ "customerNoticeId": "notice-PASC-2026-pricebook",
+ "addOnParityReview": "passed",
+ "legalApproval": "legal-apac-2026-02",
+ "migrationRunbook": "runbook-pricebook-2026-v3",
+ "salesOwner": "apac-renewals"
+ },
+ "flags": {
+ "billingDispute": false,
+ "delinquent": false,
+ "strategicAccount": true
+ }
+ }
+ ]
+}
diff --git a/grandfathered-pricing-migration-guard/package.json b/grandfathered-pricing-migration-guard/package.json
new file mode 100644
index 00000000..bc915104
--- /dev/null
+++ b/grandfathered-pricing-migration-guard/package.json
@@ -0,0 +1,11 @@
+{
+ "name": "grandfathered-pricing-migration-guard",
+ "version": "1.0.0",
+ "private": true,
+ "description": "Offline guard for legacy-price plan migrations before revenue invoices are released.",
+ "type": "commonjs",
+ "scripts": {
+ "test": "node --test",
+ "demo": "node scripts/demo.js"
+ }
+}
diff --git a/grandfathered-pricing-migration-guard/scripts/demo.js b/grandfathered-pricing-migration-guard/scripts/demo.js
new file mode 100644
index 00000000..f95e4814
--- /dev/null
+++ b/grandfathered-pricing-migration-guard/scripts/demo.js
@@ -0,0 +1,55 @@
+'use strict';
+
+const fs = require('node:fs');
+const path = require('node:path');
+const {
+ buildMarkdownReport,
+ buildSvgSummary,
+ evaluatePortfolio
+} = require('../src');
+
+const root = path.join(__dirname, '..');
+const examplesPath = path.join(root, 'examples', 'migration-packets.json');
+const artifactsDir = path.join(root, 'artifacts');
+const packet = JSON.parse(fs.readFileSync(examplesPath, 'utf8'));
+const portfolio = evaluatePortfolio(packet);
+
+fs.mkdirSync(artifactsDir, { recursive: true });
+fs.writeFileSync(
+ path.join(artifactsDir, 'pricing-migration-results.json'),
+ `${JSON.stringify(portfolio, null, 2)}\n`
+);
+fs.writeFileSync(
+ path.join(artifactsDir, 'pricing-migration-report.md'),
+ buildMarkdownReport(portfolio)
+);
+fs.writeFileSync(
+ path.join(artifactsDir, 'pricing-migration-summary.svg'),
+ buildSvgSummary(portfolio)
+);
+fs.writeFileSync(
+ path.join(artifactsDir, 'demo-transcript.md'),
+ [
+ '# Demo Transcript',
+ '',
+ '1. Load five synthetic legacy-price account packets.',
+ '2. Evaluate notice windows, price locks, consent thresholds, add-on parity, usage regressions, and reviewer evidence.',
+ '3. Emit deterministic invoice-release decisions: RELEASE_INVOICE, REVIEW_BEFORE_RELEASE, or HOLD_INVOICE.',
+ '4. Write JSON, Markdown, and SVG artifacts for reviewer replay.',
+ '',
+ '## Demo Output',
+ '',
+ `- Release: ${portfolio.summary.release}`,
+ `- Review: ${portfolio.summary.review}`,
+ `- Hold: ${portfolio.summary.hold}`,
+ `- Held accounts: ${portfolio.summary.heldAccountIds.join(', ') || 'none'}`,
+ `- Review accounts: ${portfolio.summary.reviewAccountIds.join(', ') || 'none'}`
+ ].join('\n') + '\n'
+);
+
+console.log(
+ `Generated pricing migration artifacts for ${portfolio.summary.totalAccounts} synthetic accounts.`
+);
+console.log(
+ `Release=${portfolio.summary.release} Review=${portfolio.summary.review} Hold=${portfolio.summary.hold}`
+);
diff --git a/grandfathered-pricing-migration-guard/scripts/make-demo-video.swift b/grandfathered-pricing-migration-guard/scripts/make-demo-video.swift
new file mode 100644
index 00000000..d0d5f2d1
--- /dev/null
+++ b/grandfathered-pricing-migration-guard/scripts/make-demo-video.swift
@@ -0,0 +1,183 @@
+import AVFoundation
+import CoreGraphics
+import CoreText
+import Foundation
+
+let outputPath = CommandLine.arguments.dropFirst().first ?? "artifacts/pricing-migration-demo.mp4"
+let outputURL = URL(fileURLWithPath: outputPath)
+try? FileManager.default.removeItem(at: outputURL)
+try FileManager.default.createDirectory(
+ at: outputURL.deletingLastPathComponent(),
+ withIntermediateDirectories: true
+)
+
+let width = 1280
+let height = 720
+let fps: Int32 = 30
+let totalFrames = Int(fps) * 6
+
+let writer = try AVAssetWriter(outputURL: outputURL, fileType: .mp4)
+let input = AVAssetWriterInput(
+ mediaType: .video,
+ outputSettings: [
+ AVVideoCodecKey: AVVideoCodecType.h264,
+ AVVideoWidthKey: width,
+ AVVideoHeightKey: height
+ ]
+)
+input.expectsMediaDataInRealTime = false
+
+let adaptor = AVAssetWriterInputPixelBufferAdaptor(
+ assetWriterInput: input,
+ sourcePixelBufferAttributes: [
+ kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32ARGB,
+ kCVPixelBufferWidthKey as String: width,
+ kCVPixelBufferHeightKey as String: height
+ ]
+)
+
+writer.add(input)
+writer.startWriting()
+writer.startSession(atSourceTime: .zero)
+
+func color(_ red: CGFloat, _ green: CGFloat, _ blue: CGFloat, _ alpha: CGFloat = 1) -> CGColor {
+ CGColor(red: red / 255, green: green / 255, blue: blue / 255, alpha: alpha)
+}
+
+func drawText(
+ _ context: CGContext,
+ _ text: String,
+ x: CGFloat,
+ y: CGFloat,
+ size: CGFloat,
+ color textColor: CGColor,
+ weight: String = "Regular"
+) {
+ let font = CTFontCreateWithName("Helvetica-\(weight)" as CFString, size, nil)
+ let attributes: [CFString: Any] = [
+ kCTFontAttributeName: font,
+ kCTForegroundColorAttributeName: textColor
+ ]
+ let attributed = CFAttributedStringCreate(nil, text as CFString, attributes as CFDictionary)!
+ let line = CTLineCreateWithAttributedString(attributed)
+ context.textPosition = CGPoint(x: x, y: y)
+ CTLineDraw(line, context)
+}
+
+func fillRounded(_ context: CGContext, rect: CGRect, radius: CGFloat, fill: CGColor) {
+ let path = CGPath(roundedRect: rect, cornerWidth: radius, cornerHeight: radius, transform: nil)
+ context.addPath(path)
+ context.setFillColor(fill)
+ context.fillPath()
+}
+
+func renderFrame(_ buffer: CVPixelBuffer, frame: Int) {
+ CVPixelBufferLockBaseAddress(buffer, [])
+ defer { CVPixelBufferUnlockBaseAddress(buffer, []) }
+
+ let context = CGContext(
+ data: CVPixelBufferGetBaseAddress(buffer),
+ width: width,
+ height: height,
+ bitsPerComponent: 8,
+ bytesPerRow: CVPixelBufferGetBytesPerRow(buffer),
+ space: CGColorSpaceCreateDeviceRGB(),
+ bitmapInfo: CGImageAlphaInfo.noneSkipFirst.rawValue
+ )!
+
+ context.setFillColor(color(248, 250, 252))
+ context.fill(CGRect(x: 0, y: 0, width: width, height: height))
+ context.translateBy(x: 0, y: CGFloat(height))
+ context.scaleBy(x: 1, y: -1)
+
+ let progress = min(1, CGFloat(frame) / CGFloat(totalFrames - 1))
+ let reveal = min(1, progress * 1.35)
+
+ drawText(
+ context,
+ "Grandfathered Pricing Migration Guard",
+ x: 72,
+ y: 92,
+ size: 44,
+ color: color(17, 24, 39),
+ weight: "Bold"
+ )
+ drawText(
+ context,
+ "Revenue Infrastructure issue #20 - synthetic replay packet",
+ x: 74,
+ y: 132,
+ size: 22,
+ color: color(71, 85, 105)
+ )
+
+ fillRounded(context, rect: CGRect(x: 74, y: 178, width: 1132, height: 84), radius: 16, fill: color(255, 255, 255))
+ drawText(context, "Portfolio result", x: 104, y: 214, size: 24, color: color(15, 23, 42), weight: "Bold")
+ drawText(context, "1 release | 1 review | 3 holds", x: 104, y: 246, size: 26, color: color(30, 41, 59))
+
+ let barY: CGFloat = 305
+ let maxWidth: CGFloat = 1000 * reveal
+ fillRounded(context, rect: CGRect(x: 74, y: barY, width: maxWidth * 0.2, height: 36), radius: 8, fill: color(22, 163, 74))
+ fillRounded(context, rect: CGRect(x: 74 + maxWidth * 0.2, y: barY, width: maxWidth * 0.2, height: 36), radius: 8, fill: color(202, 138, 4))
+ fillRounded(context, rect: CGRect(x: 74 + maxWidth * 0.4, y: barY, width: maxWidth * 0.6, height: 36), radius: 8, fill: color(234, 88, 12))
+
+ let rows = [
+ ("Northbridge University Research Office", "RELEASE_INVOICE", color(21, 128, 61)),
+ ("Helix Bioinformatics Studio", "REVIEW_BEFORE_RELEASE", color(161, 98, 7)),
+ ("Meridian Lab Collaborative", "HOLD_INVOICE", color(194, 65, 12)),
+ ("Civic Methods Consortium", "HOLD_INVOICE", color(194, 65, 12)),
+ ("Pacific Applied Science Center", "HOLD_INVOICE", color(194, 65, 12))
+ ]
+
+ for (index, row) in rows.enumerated() {
+ let y = CGFloat(390 + index * 44)
+ let rowReveal = progress > CGFloat(index) * 0.12 ? CGFloat(1) : CGFloat(0)
+ if rowReveal > 0 {
+ fillRounded(context, rect: CGRect(x: 74, y: y - 24, width: 1132, height: 34), radius: 8, fill: color(255, 255, 255))
+ drawText(context, row.0, x: 98, y: y, size: 20, color: color(30, 41, 59))
+ drawText(context, row.1, x: 820, y: y, size: 20, color: row.2, weight: "Bold")
+ }
+ }
+
+ drawText(
+ context,
+ "Checks: notice window, price lock, consent threshold, add-on parity, usage regression, reviewer evidence.",
+ x: 74,
+ y: 650,
+ size: 19,
+ color: color(71, 85, 105)
+ )
+}
+
+for frame in 0.. 0 ? 100 : 0;
+ return ((toCents - fromCents) / fromCents) * 100;
+}
+
+function roundPercent(value) {
+ return Number(value.toFixed(2));
+}
+
+function unique(values) {
+ return [...new Set(values.filter(Boolean))];
+}
+
+function classify(current, incoming) {
+ return SEVERITY_RANK[incoming] > SEVERITY_RANK[current] ? incoming : current;
+}
+
+function formatMoney(cents, currency) {
+ const sign = cents < 0 ? '-' : '';
+ const absolute = Math.abs(cents);
+ return `${sign}${currency} ${(absolute / 100).toLocaleString('en-US', {
+ minimumFractionDigits: 2,
+ maximumFractionDigits: 2
+ })}`;
+}
+
+function getTargetCurrency(account) {
+ return account.targetPlan.currency || account.currency;
+}
+
+function findMissingAddOns(currentAddOns, targetAddOns) {
+ const target = new Set(targetAddOns || []);
+ return (currentAddOns || []).filter((addOn) => !target.has(addOn));
+}
+
+function evaluateAccount(account, options = {}) {
+ const policy = { ...DEFAULT_POLICY, ...(options.policy || {}) };
+ const asOf = parseDate(options.asOf, 'asOf') || new Date();
+ const current = account.currentPlan || {};
+ const target = account.targetPlan || {};
+ const contract = account.contract || {};
+ const usage = account.usage || {};
+ const discounts = account.discounts || {};
+ const evidence = account.evidence || {};
+ const flags = account.flags || {};
+
+ let decision = 'RELEASE_INVOICE';
+ const reasons = [];
+ const actions = [];
+
+ function add(severity, code, message, action) {
+ decision = classify(decision, severity);
+ reasons.push({ severity, code, message });
+ if (action) actions.push(action);
+ }
+
+ const currentPrice = Number(current.monthlyPriceCents || 0);
+ const targetPrice = Number(target.monthlyPriceCents || 0);
+ const priceDeltaCents = targetPrice - currentPrice;
+ const increasePercent = roundPercent(percentChange(currentPrice, targetPrice));
+ const targetCurrency = getTargetCurrency(account);
+
+ const priceLockUntil = parseDate(contract.priceLockUntil, 'contract.priceLockUntil');
+ const grandfatheredUntil = parseDate(contract.grandfatheredUntil, 'contract.grandfatheredUntil');
+ const noticeSentAt = parseDate(contract.noticeSentAt, 'contract.noticeSentAt');
+
+ if (priceDeltaCents > 0 && priceLockUntil && asOf <= priceLockUntil) {
+ add(
+ 'HOLD_INVOICE',
+ 'ACTIVE_PRICE_LOCK',
+ `Price lock remains active until ${contract.priceLockUntil}.`,
+ 'Keep the account on the legacy price book until the price lock expires or a new order form is signed.'
+ );
+ }
+
+ if (priceDeltaCents > 0 && grandfatheredUntil) {
+ if (asOf <= grandfatheredUntil && contract.allowsAutoMigration === false) {
+ add(
+ 'HOLD_INVOICE',
+ 'GRANDFATHERING_BLOCKS_AUTO_MIGRATION',
+ `Contract is grandfathered until ${contract.grandfatheredUntil} and does not allow automatic migration.`,
+ 'Request explicit customer approval before creating a new-price invoice.'
+ );
+ } else if (asOf > grandfatheredUntil) {
+ const daysAfterGrandfathering = daysBetween(grandfatheredUntil, asOf);
+ if (daysAfterGrandfathering <= policy.legacyGraceDays) {
+ add(
+ 'REVIEW_BEFORE_RELEASE',
+ 'INSIDE_LEGACY_GRACE_PERIOD',
+ `Grandfathering ended ${daysAfterGrandfathering} days ago, inside the ${policy.legacyGraceDays}-day review grace period.`,
+ 'Route through renewals review before release.'
+ );
+ }
+ }
+ }
+
+ if (priceDeltaCents > 0) {
+ if (!noticeSentAt) {
+ add(
+ 'HOLD_INVOICE',
+ 'MISSING_CUSTOMER_NOTICE',
+ 'No customer price-change notice date is present.',
+ 'Send customer notice and restart the notice clock.'
+ );
+ } else {
+ const noticeAgeDays = daysBetween(noticeSentAt, asOf);
+ if (noticeAgeDays < policy.noticeDays) {
+ add(
+ 'HOLD_INVOICE',
+ 'NOTICE_WINDOW_NOT_MET',
+ `Customer notice is ${noticeAgeDays} days old; policy requires ${policy.noticeDays} days.`,
+ 'Hold invoice release until the notice window is satisfied.'
+ );
+ }
+ }
+ }
+
+ const consentThreshold = Number(
+ contract.requiresConsentAbovePct ?? policy.defaultConsentThresholdPercent
+ );
+ if (priceDeltaCents > 0 && increasePercent >= consentThreshold && !contract.consentReceivedAt) {
+ add(
+ 'HOLD_INVOICE',
+ 'MISSING_MATERIAL_INCREASE_CONSENT',
+ `Increase is ${increasePercent}% and requires consent above ${consentThreshold}%.`,
+ 'Collect explicit consent or signed renewal before migration.'
+ );
+ }
+
+ if (increasePercent >= policy.materialIncreasePercent && !evidence.legalApproval) {
+ add(
+ 'REVIEW_BEFORE_RELEASE',
+ 'MATERIAL_INCREASE_NEEDS_LEGAL_REVIEW',
+ `Increase is ${increasePercent}%, above the ${policy.materialIncreasePercent}% materiality threshold.`,
+ 'Attach legal approval before release.'
+ );
+ }
+
+ if (account.currency !== targetCurrency && !evidence.financeApproval) {
+ add(
+ 'HOLD_INVOICE',
+ 'CURRENCY_CHANGE_WITHOUT_FINANCE_APPROVAL',
+ `Currency changes from ${account.currency} to ${targetCurrency}.`,
+ 'Add finance approval and settlement notes for the currency migration.'
+ );
+ }
+
+ if (usage.activeSeats && target.includedSeats && target.includedSeats < usage.activeSeats) {
+ add(
+ 'HOLD_INVOICE',
+ 'SEAT_CAP_REGRESSION',
+ `Target plan includes ${target.includedSeats} seats but current usage has ${usage.activeSeats}.`,
+ 'Increase target seat allowance or collect admin confirmation for seat reduction.'
+ );
+ }
+
+ if (
+ usage.averageComputeHours &&
+ target.includedComputeHours &&
+ current.includedComputeHours &&
+ target.includedComputeHours < usage.averageComputeHours
+ ) {
+ const computeRegressionPercent = roundPercent(
+ ((current.includedComputeHours - target.includedComputeHours) /
+ current.includedComputeHours) *
+ 100
+ );
+ const severity =
+ computeRegressionPercent > policy.maxComputeRegressionPercent
+ ? 'HOLD_INVOICE'
+ : 'REVIEW_BEFORE_RELEASE';
+ add(
+ severity,
+ 'COMPUTE_ENTITLEMENT_REGRESSION',
+ `Target compute allowance is ${target.includedComputeHours} hours while recent usage is ${usage.averageComputeHours}.`,
+ 'Confirm customer-facing compute entitlement changes before billing.'
+ );
+ }
+
+ const missingAddOns = findMissingAddOns(current.addOns, target.addOns);
+ if (missingAddOns.length > 0) {
+ add(
+ 'HOLD_INVOICE',
+ 'ADD_ON_PARITY_GAP',
+ `Target plan is missing legacy add-ons: ${missingAddOns.join(', ')}.`,
+ 'Add equivalent add-ons or document an accepted replacement.'
+ );
+ } else if ((current.addOns || []).length > 0 && evidence.addOnParityReview !== 'passed') {
+ add(
+ 'REVIEW_BEFORE_RELEASE',
+ 'ADD_ON_PARITY_REVIEW_MISSING',
+ 'Legacy add-ons are present but no passed parity review is attached.',
+ 'Attach add-on parity review evidence.'
+ );
+ }
+
+ if (discounts.annualCommitment && target.billingCycle !== 'annual') {
+ add(
+ 'REVIEW_BEFORE_RELEASE',
+ 'ANNUAL_COMMITMENT_BILLING_CYCLE_CHANGE',
+ 'Account has an annual commitment but target plan is not annual.',
+ 'Review annual discount compatibility before release.'
+ );
+ }
+
+ if (discounts.volumePercent > 0 && target.includedSeats < current.includedSeats) {
+ add(
+ 'REVIEW_BEFORE_RELEASE',
+ 'VOLUME_DISCOUNT_SEAT_BASE_CHANGED',
+ `Volume discount ${discounts.volumePercent}% is tied to ${current.includedSeats} legacy seats.`,
+ 'Confirm volume discount recalculation with revenue operations.'
+ );
+ }
+
+ if (!contract.signedOrderFormAt && account.segment === 'institution') {
+ add(
+ 'REVIEW_BEFORE_RELEASE',
+ 'ORDER_FORM_EVIDENCE_MISSING',
+ 'Institutional account does not have signed order form evidence.',
+ 'Attach signed order form or renewal packet.'
+ );
+ }
+
+ if (!evidence.migrationRunbook) {
+ add(
+ 'REVIEW_BEFORE_RELEASE',
+ 'MIGRATION_RUNBOOK_MISSING',
+ 'No migration runbook is linked for reviewer replay.',
+ 'Attach the price-book migration runbook.'
+ );
+ }
+
+ if (flags.billingDispute) {
+ add(
+ 'HOLD_INVOICE',
+ 'ACTIVE_BILLING_DISPUTE',
+ 'Account has an active billing dispute.',
+ 'Resolve the dispute before releasing a migration invoice.'
+ );
+ }
+
+ if (flags.delinquent) {
+ add(
+ 'HOLD_INVOICE',
+ 'DELINQUENT_ACCOUNT',
+ 'Account is delinquent and should not be silently migrated.',
+ 'Move the account through collections or customer-success review.'
+ );
+ }
+
+ if (flags.strategicAccount && !evidence.salesOwner) {
+ add(
+ 'REVIEW_BEFORE_RELEASE',
+ 'STRATEGIC_ACCOUNT_OWNER_MISSING',
+ 'Strategic account lacks a sales-owner signoff.',
+ 'Attach named sales owner or renewal owner signoff.'
+ );
+ }
+
+ const sortedReasons = reasons.sort(
+ (a, b) => SEVERITY_RANK[b.severity] - SEVERITY_RANK[a.severity] || a.code.localeCompare(b.code)
+ );
+
+ return {
+ accountId: account.id,
+ accountName: account.name,
+ segment: account.segment,
+ decision,
+ priceImpact: {
+ currentMonthlyCents: currentPrice,
+ targetMonthlyCents: targetPrice,
+ deltaMonthlyCents: priceDeltaCents,
+ increasePercent,
+ displayDelta: formatMoney(priceDeltaCents, targetCurrency)
+ },
+ entitlementImpact: {
+ seatDelta: Number(target.includedSeats || 0) - Number(current.includedSeats || 0),
+ computeHourDelta:
+ Number(target.includedComputeHours || 0) - Number(current.includedComputeHours || 0),
+ missingAddOns
+ },
+ evidence: {
+ customerNoticeId: evidence.customerNoticeId || null,
+ consentReceivedAt: contract.consentReceivedAt || null,
+ financeApproval: evidence.financeApproval || null,
+ legalApproval: evidence.legalApproval || null,
+ migrationRunbook: evidence.migrationRunbook || null,
+ salesOwner: evidence.salesOwner || null
+ },
+ reasons: sortedReasons,
+ actions: unique(actions),
+ riskScore: sortedReasons.reduce((total, reason) => total + SEVERITY_RANK[reason.severity], 0)
+ };
+}
+
+function evaluatePortfolio(packet, options = {}) {
+ const asOf = options.asOf || packet.asOf;
+ const policy = { ...DEFAULT_POLICY, ...(packet.policy || {}), ...(options.policy || {}) };
+ const results = (packet.accounts || []).map((account) =>
+ evaluateAccount(account, { asOf, policy })
+ );
+ return {
+ asOf,
+ policy,
+ summary: summarizeResults(results),
+ results
+ };
+}
+
+function summarizeResults(results) {
+ const summary = {
+ totalAccounts: results.length,
+ release: 0,
+ review: 0,
+ hold: 0,
+ totalMonthlyDeltaCents: 0,
+ heldAccountIds: [],
+ reviewAccountIds: [],
+ topRisks: []
+ };
+
+ for (const result of results) {
+ summary.totalMonthlyDeltaCents += result.priceImpact.deltaMonthlyCents;
+ if (result.decision === 'RELEASE_INVOICE') summary.release += 1;
+ if (result.decision === 'REVIEW_BEFORE_RELEASE') {
+ summary.review += 1;
+ summary.reviewAccountIds.push(result.accountId);
+ }
+ if (result.decision === 'HOLD_INVOICE') {
+ summary.hold += 1;
+ summary.heldAccountIds.push(result.accountId);
+ }
+ }
+
+ summary.topRisks = results
+ .flatMap((result) =>
+ result.reasons.map((reason) => ({
+ accountId: result.accountId,
+ severity: reason.severity,
+ code: reason.code
+ }))
+ )
+ .sort((a, b) => SEVERITY_RANK[b.severity] - SEVERITY_RANK[a.severity])
+ .slice(0, 8);
+
+ return summary;
+}
+
+function buildMarkdownReport(portfolio) {
+ const lines = [
+ '# Grandfathered Pricing Migration Report',
+ '',
+ `As of: ${portfolio.asOf}`,
+ '',
+ '## Summary',
+ '',
+ `- Total accounts: ${portfolio.summary.totalAccounts}`,
+ `- Release: ${portfolio.summary.release}`,
+ `- Review: ${portfolio.summary.review}`,
+ `- Hold: ${portfolio.summary.hold}`,
+ `- Monthly revenue delta: ${formatMoney(portfolio.summary.totalMonthlyDeltaCents, 'USD')} before currency-specific settlement review`,
+ '',
+ '## Account Decisions',
+ '',
+ '| Account | Decision | Monthly delta | Primary reason |',
+ '| --- | --- | ---: | --- |'
+ ];
+
+ for (const result of portfolio.results) {
+ const primary = result.reasons[0]
+ ? `${result.reasons[0].code}: ${result.reasons[0].message}`
+ : 'All release checks passed';
+ lines.push(
+ `| ${result.accountName} | ${result.decision} | ${result.priceImpact.displayDelta} | ${primary} |`
+ );
+ }
+
+ lines.push('', '## Release Actions', '');
+ for (const result of portfolio.results) {
+ lines.push(`### ${result.accountName}`);
+ if (result.actions.length === 0) {
+ lines.push('- Release invoice under the target price book.');
+ } else {
+ for (const action of result.actions) lines.push(`- ${action}`);
+ }
+ lines.push('');
+ }
+
+ while (lines[lines.length - 1] === '') lines.pop();
+ return `${lines.join('\n')}\n`;
+}
+
+function buildSvgSummary(portfolio) {
+ const width = 860;
+ const height = 360;
+ const releaseWidth = portfolio.summary.release * 90;
+ const reviewWidth = portfolio.summary.review * 90;
+ const holdWidth = portfolio.summary.hold * 90;
+ const escape = (value) =>
+ String(value)
+ .replace(/&/g, '&')
+ .replace(//g, '>');
+
+ const rows = portfolio.results
+ .map((result, index) => {
+ const y = 176 + index * 30;
+ const color =
+ result.decision === 'HOLD_INVOICE'
+ ? '#c2410c'
+ : result.decision === 'REVIEW_BEFORE_RELEASE'
+ ? '#a16207'
+ : '#15803d';
+ return `${escape(result.accountName)}${result.decision}`;
+ })
+ .join('');
+
+ return `
+`;
+}
+
+module.exports = {
+ DEFAULT_POLICY,
+ buildMarkdownReport,
+ buildSvgSummary,
+ evaluateAccount,
+ evaluatePortfolio,
+ formatMoney,
+ summarizeResults
+};
diff --git a/grandfathered-pricing-migration-guard/test/pricingMigrationGuard.test.js b/grandfathered-pricing-migration-guard/test/pricingMigrationGuard.test.js
new file mode 100644
index 00000000..2dbf818a
--- /dev/null
+++ b/grandfathered-pricing-migration-guard/test/pricingMigrationGuard.test.js
@@ -0,0 +1,96 @@
+'use strict';
+
+const assert = require('node:assert/strict');
+const fs = require('node:fs');
+const path = require('node:path');
+const test = require('node:test');
+const {
+ buildMarkdownReport,
+ buildSvgSummary,
+ evaluateAccount,
+ evaluatePortfolio
+} = require('../src');
+
+const packet = JSON.parse(
+ fs.readFileSync(path.join(__dirname, '..', 'examples', 'migration-packets.json'), 'utf8')
+);
+
+function account(id) {
+ return packet.accounts.find((entry) => entry.id === id);
+}
+
+test('releases a legacy account when notice, consent, entitlements, and evidence pass', () => {
+ const result = evaluateAccount(account('acct-univ-ready'), {
+ asOf: packet.asOf,
+ policy: packet.policy
+ });
+
+ assert.equal(result.decision, 'RELEASE_INVOICE');
+ assert.equal(result.reasons.length, 0);
+ assert.equal(result.entitlementImpact.missingAddOns.length, 0);
+ assert.equal(result.priceImpact.increasePercent, 9.29);
+});
+
+test('holds migration invoices while a price lock blocks automatic migration', () => {
+ const result = evaluateAccount(account('acct-lab-price-lock'), {
+ asOf: packet.asOf,
+ policy: packet.policy
+ });
+
+ assert.equal(result.decision, 'HOLD_INVOICE');
+ assert.ok(result.reasons.some((reason) => reason.code === 'ACTIVE_PRICE_LOCK'));
+ assert.ok(
+ result.reasons.some((reason) => reason.code === 'GRANDFATHERING_BLOCKS_AUTO_MIGRATION')
+ );
+});
+
+test('holds accounts when notice windows, consent, and add-on parity are missing', () => {
+ const result = evaluateAccount(account('acct-notice-gap'), {
+ asOf: packet.asOf,
+ policy: packet.policy
+ });
+
+ assert.equal(result.decision, 'HOLD_INVOICE');
+ assert.ok(result.reasons.some((reason) => reason.code === 'NOTICE_WINDOW_NOT_MET'));
+ assert.ok(result.reasons.some((reason) => reason.code === 'MISSING_MATERIAL_INCREASE_CONSENT'));
+ assert.ok(result.reasons.some((reason) => reason.code === 'ADD_ON_PARITY_GAP'));
+ assert.ok(result.reasons.some((reason) => reason.code === 'SEAT_CAP_REGRESSION'));
+});
+
+test('routes recently expired grandfathering windows to review before release', () => {
+ const result = evaluateAccount(account('acct-grace-review'), {
+ asOf: packet.asOf,
+ policy: packet.policy
+ });
+
+ assert.equal(result.decision, 'REVIEW_BEFORE_RELEASE');
+ assert.ok(result.reasons.some((reason) => reason.code === 'INSIDE_LEGACY_GRACE_PERIOD'));
+ assert.ok(
+ result.reasons.some((reason) => reason.code === 'MATERIAL_INCREASE_NEEDS_LEGAL_REVIEW')
+ );
+});
+
+test('portfolio summaries count release, review, and hold decisions deterministically', () => {
+ const portfolio = evaluatePortfolio(packet);
+
+ assert.equal(portfolio.summary.totalAccounts, 5);
+ assert.equal(portfolio.summary.release, 1);
+ assert.equal(portfolio.summary.review, 1);
+ assert.equal(portfolio.summary.hold, 3);
+ assert.deepEqual(portfolio.summary.heldAccountIds, [
+ 'acct-lab-price-lock',
+ 'acct-notice-gap',
+ 'acct-currency-mismatch'
+ ]);
+});
+
+test('review artifacts include account decisions and accessible SVG labels', () => {
+ const portfolio = evaluatePortfolio(packet);
+ const report = buildMarkdownReport(portfolio);
+ const svg = buildSvgSummary(portfolio);
+
+ assert.match(report, /Grandfathered Pricing Migration Report/);
+ assert.match(report, /CURRENCY_CHANGE_WITHOUT_FINANCE_APPROVAL/);
+ assert.match(svg, /Grandfathered pricing migration guard summary/);
+ assert.match(svg, /HOLD_INVOICE/);
+});