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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions fx-settlement-invoice-guard/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# FX Settlement Invoice Guard

Self-contained guard for SCIBASE issue #20, focused on revenue infrastructure.

The module validates synthetic multi-currency invoice packets before invoice release or revenue close. It catches stale FX snapshots, unsupported currency minor units, settlement mismatches, rounding drift, missing processor references, credit-note offsets, and missing finance approval for material FX variance.

## Scope

- invoice currency, account currency, and settlement currency consistency
- FX snapshot age and source metadata
- currency minor-unit rounding
- expected settlement amount vs processor settlement amount
- credit note and refund offset handling
- material FX variance approval requirements
- deterministic release/hold/block decisions

## Decisions

- `RELEASE_INVOICE`
- `REVIEW_BEFORE_RELEASE`
- `HOLD_INVOICE`

## Local Validation

```bash
npm test
npm run demo
node --check src/index.js
node --check scripts/demo.js
node --check test/fxSettlementInvoiceGuard.test.js
git diff --check
```

See `REQUIREMENT_MAP.md` for the issue-to-evidence map and explicit non-scope boundaries.

## Boundaries

- synthetic invoice packets only
- no payment processor calls
- no live FX market data
- no bank details
- no credentials
- no private customer data
26 changes: 26 additions & 0 deletions fx-settlement-invoice-guard/REQUIREMENT_MAP.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Requirement Map

This module is scoped to invoice-release currency rounding controls for issue #20 Revenue Infrastructure. It is intentionally narrower than broader FX settlement reconciliation work.

| Issue #20 requirement area | Covered by this module | Evidence |
| --- | --- | --- |
| Tiered subscription and institutional invoices | Validates synthetic invoice packets before release or revenue close | `examples/fx-settlement-packets.json` |
| Usage-based AI compute and top-ups | Includes compute bundle and top-up line items in multi-currency invoices | `examples/fx-settlement-packets.json` |
| Secure payment integrations | Requires processor settlement reference before revenue close | `src/index.js` |
| Monthly/annual cycles and credits | Applies credit notes and requires credit approval evidence | `src/index.js` |
| Transparent usage and finance controls | Emits deterministic release/review/hold findings with remediation | `src/index.js`, `artifacts/fx-settlement-report.md` |
| Reviewer demo evidence | Generates JSON, Markdown, SVG, and MP4 artifacts | `scripts/demo.js`, `artifacts/` |

## Explicit Non-Scope

The guard does not implement:

- provider fee caps
- remittance reference deduplication
- cross-border tax evidence
- booking ledger reconciliation
- payment processor API integration
- live FX market lookup
- bank account or customer payment data handling

Those are separate revenue infrastructure slices. This PR focuses only on whether a synthetic invoice can be released when currency minor units, FX quote freshness, processor settlement delta tolerance, credit offsets, and material variance approvals are coherent.
Binary file not shown.
43 changes: 43 additions & 0 deletions fx-settlement-invoice-guard/artifacts/fx-settlement-report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# FX Settlement Invoice Guard Report

Summary: blocks=4, reviews=5, passes=1

## inv:lab-pro-eur-042

- Account: acct:eu-lab-17
- Currency: EUR -> USD
- Expected settlement: 2808
- Decision: RELEASE_INVOICE

| Severity | Code | Message | Remediation |
| --- | --- | --- | --- |
| pass | fx_settlement_ready | Invoice FX settlement, rounding, credits, and processor references are release-ready. | No remediation required. |

## inv:consortium-gbp-105

- Account: acct:uk-consortium-5
- Currency: GBP -> USD
- Expected settlement: 1905
- Decision: REVIEW_BEFORE_RELEASE

| Severity | Code | Message | Remediation |
| --- | --- | --- | --- |
| review | fx_snapshot_stale | FX snapshot is older than the allowed release window. | Refresh FX snapshot or attach finance approval for stale-rate usage. |
| review | fx_quote_before_invoice_window | FX quote predates invoice issue time beyond the allowed window. | Use an invoice-time quote or document policy approval. |
| review | fx_settlement_variance | Processor settlement differs from expected settlement by -0.02 USD. | Route to finance review or attach rounding-policy evidence. |

## inv:jp-enterprise-219

- Account: acct:jp-enterprise-3
- Currency: JPY -> USD
- Expected settlement: 6208
- Decision: HOLD_INVOICE

| Severity | Code | Message | Remediation |
| --- | --- | --- | --- |
| block | minor_unit_rounding_invalid | grossInvoice 1020000.55 has too many decimals for JPY. | Round grossInvoice to 0 minor units before invoice release. |
| block | minor_unit_rounding_invalid | netInvoice 970000.55 has too many decimals for JPY. | Round netInvoice to 0 minor units before invoice release. |
| review | fx_snapshot_stale | FX snapshot is older than the allowed release window. | Refresh FX snapshot or attach finance approval for stale-rate usage. |
| block | processor_reference_missing | Processor settlement reference is missing. | Attach payment processor settlement id before revenue close. |
| block | material_fx_settlement_variance | Processor settlement differs from expected settlement by -708 USD. | Attach finance approval and block release until variance is resolved. |
| review | credit_offset_approval_missing | Credit notes are applied without a credit approval id. | Attach approved credit memo before invoice release. |
110 changes: 110 additions & 0 deletions fx-settlement-invoice-guard/artifacts/fx-settlement-results.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
{
"generatedAt": "2026-06-13T16:00:38.029Z",
"summary": {
"blocks": 4,
"reviews": 5,
"passes": 1
},
"results": [
{
"invoiceId": "inv:lab-pro-eur-042",
"accountId": "acct:eu-lab-17",
"invoiceCurrency": "EUR",
"settlementCurrency": "USD",
"expectedSettlement": 2808,
"decision": "RELEASE_INVOICE",
"findings": [
{
"severity": "pass",
"code": "fx_settlement_ready",
"message": "Invoice FX settlement, rounding, credits, and processor references are release-ready.",
"remediation": "No remediation required.",
"ref": "inv:lab-pro-eur-042"
}
]
},
{
"invoiceId": "inv:consortium-gbp-105",
"accountId": "acct:uk-consortium-5",
"invoiceCurrency": "GBP",
"settlementCurrency": "USD",
"expectedSettlement": 1905,
"decision": "REVIEW_BEFORE_RELEASE",
"findings": [
{
"severity": "review",
"code": "fx_snapshot_stale",
"message": "FX snapshot is older than the allowed release window.",
"remediation": "Refresh FX snapshot or attach finance approval for stale-rate usage.",
"ref": "inv:consortium-gbp-105"
},
{
"severity": "review",
"code": "fx_quote_before_invoice_window",
"message": "FX quote predates invoice issue time beyond the allowed window.",
"remediation": "Use an invoice-time quote or document policy approval.",
"ref": "inv:consortium-gbp-105"
},
{
"severity": "review",
"code": "fx_settlement_variance",
"message": "Processor settlement differs from expected settlement by -0.02 USD.",
"remediation": "Route to finance review or attach rounding-policy evidence.",
"ref": "inv:consortium-gbp-105"
}
]
},
{
"invoiceId": "inv:jp-enterprise-219",
"accountId": "acct:jp-enterprise-3",
"invoiceCurrency": "JPY",
"settlementCurrency": "USD",
"expectedSettlement": 6208,
"decision": "HOLD_INVOICE",
"findings": [
{
"severity": "block",
"code": "minor_unit_rounding_invalid",
"message": "grossInvoice 1020000.55 has too many decimals for JPY.",
"remediation": "Round grossInvoice to 0 minor units before invoice release.",
"ref": "grossInvoice"
},
{
"severity": "block",
"code": "minor_unit_rounding_invalid",
"message": "netInvoice 970000.55 has too many decimals for JPY.",
"remediation": "Round netInvoice to 0 minor units before invoice release.",
"ref": "netInvoice"
},
{
"severity": "review",
"code": "fx_snapshot_stale",
"message": "FX snapshot is older than the allowed release window.",
"remediation": "Refresh FX snapshot or attach finance approval for stale-rate usage.",
"ref": "inv:jp-enterprise-219"
},
{
"severity": "block",
"code": "processor_reference_missing",
"message": "Processor settlement reference is missing.",
"remediation": "Attach payment processor settlement id before revenue close.",
"ref": "inv:jp-enterprise-219"
},
{
"severity": "block",
"code": "material_fx_settlement_variance",
"message": "Processor settlement differs from expected settlement by -708 USD.",
"remediation": "Attach finance approval and block release until variance is resolved.",
"ref": "inv:jp-enterprise-219"
},
{
"severity": "review",
"code": "credit_offset_approval_missing",
"message": "Credit notes are applied without a credit approval id.",
"remediation": "Attach approved credit memo before invoice release.",
"ref": "inv:jp-enterprise-219"
}
]
}
]
}
20 changes: 20 additions & 0 deletions fx-settlement-invoice-guard/artifacts/fx-settlement-summary.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
76 changes: 76 additions & 0 deletions fx-settlement-invoice-guard/examples/fx-settlement-packets.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
[
{
"invoiceId": "inv:lab-pro-eur-042",
"accountId": "acct:eu-lab-17",
"invoiceCurrency": "EUR",
"settlementCurrency": "USD",
"accountCurrency": "EUR",
"invoiceIssuedAt": "2026-06-13T12:00:00Z",
"releaseAt": "2026-06-13T14:00:00Z",
"lineItems": [
{ "description": "Lab Pro annual subscription", "amount": 2400 },
{ "description": "AI compute top-up", "amount": 300 }
],
"creditNotes": [
{ "creditNoteId": "cred:trial-conversion-9", "amount": 100 }
],
"creditApprovalId": "approval:credit-881",
"fxSnapshot": {
"source": "treasury-policy-table",
"rateId": "fx:eur-usd:2026-06-13T13",
"rate": 1.08,
"quotedAt": "2026-06-13T13:00:00Z"
},
"processorReference": "stripe:settle:ok-4242",
"processorSettlementAmount": 2808,
"materialVarianceThreshold": 25
},
{
"invoiceId": "inv:consortium-gbp-105",
"accountId": "acct:uk-consortium-5",
"invoiceCurrency": "GBP",
"settlementCurrency": "USD",
"accountCurrency": "GBP",
"invoiceIssuedAt": "2026-06-13T09:30:00Z",
"releaseAt": "2026-06-14T12:00:00Z",
"lineItems": [
{ "description": "Consortium analytics API", "amount": 1250 },
{ "description": "Priority support", "amount": 250 }
],
"creditNotes": [],
"fxSnapshot": {
"source": "treasury-policy-table",
"rateId": "fx:gbp-usd:2026-06-12T08",
"rate": 1.27,
"quotedAt": "2026-06-12T08:00:00Z"
},
"processorReference": "stripe:settle:review-105",
"processorSettlementAmount": 1904.98,
"materialVarianceThreshold": 25
},
{
"invoiceId": "inv:jp-enterprise-219",
"accountId": "acct:jp-enterprise-3",
"invoiceCurrency": "JPY",
"settlementCurrency": "USD",
"accountCurrency": "JPY",
"invoiceIssuedAt": "2026-06-13T05:00:00Z",
"releaseAt": "2026-06-13T08:00:00Z",
"lineItems": [
{ "description": "Institutional license", "amount": 900000 },
{ "description": "Compute bundle", "amount": 120000.55 }
],
"creditNotes": [
{ "creditNoteId": "cred:service-credit-44", "amount": 50000 }
],
"fxSnapshot": {
"source": "treasury-policy-table",
"rateId": "fx:jpy-usd:2026-06-13T06",
"rate": 0.0064,
"quotedAt": "2026-06-13T06:00:00Z"
},
"processorReference": "",
"processorSettlementAmount": 5500,
"materialVarianceThreshold": 25
}
]
11 changes: 11 additions & 0 deletions fx-settlement-invoice-guard/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"name": "fx-settlement-invoice-guard",
"version": "1.0.0",
"private": true,
"description": "Synthetic multi-currency invoice settlement guard for revenue infrastructure.",
"type": "commonjs",
"scripts": {
"test": "node test/fxSettlementInvoiceGuard.test.js",
"demo": "node scripts/demo.js"
}
}
Loading