Skip to content

feat: make invoice-verification thresholds configurable with tenant o…#542

Open
extolkom wants to merge 1 commit into
Liquifact:mainfrom
extolkom:enhancement/verification-configurable-thresholds
Open

feat: make invoice-verification thresholds configurable with tenant o…#542
extolkom wants to merge 1 commit into
Liquifact:mainfrom
extolkom:enhancement/verification-configurable-thresholds

Conversation

@extolkom

Copy link
Copy Markdown
Contributor

closes #438

Summary
The invoice-verification service hardcoded two amount limits in src/services/invoiceVerification.js — the fraud-rejection ceiling (10,000,000) and the manual-review threshold (1,000,000). These literals couldn't adapt to different currencies, tenant risk appetites, or limit changes without a code change and redeploy. This PR moves them into environment-backed configuration with optional per-tenant overrides, and adds a stable machine-readable reason code to every decision.

Changes
New src/config/verificationThresholds.js — resolves thresholds from the environment with documented defaults that preserve current behavior:
INVOICE_FRAUD_CEILING (default 10000000)
INVOICE_MANUAL_REVIEW_THRESHOLD (default 1000000)
INVOICE_TENANT_THRESHOLDS — JSON map of per-tenant overrides; each entry may set either or both fields, with omitted fields falling back to the global defaults.
src/services/invoiceVerification.js — now resolves thresholds via config (optionally per-tenant), emits a machine-readable reasonCode on every reject/manual-review outcome, and keeps the decision order and human-readable reason strings unchanged. verifyInvoice(payload, { tenantId }) takes the tenant id from a trusted second argument.
Docs — docs/configuration.md documents the thresholds, override format, validation rules, and the full reason-code table; .env.example documents the new vars (defaults shown, commented).
Behavior & validation
Defaults reproduce the prior behavior exactly — a deployment that sets nothing sees no change.
Each value must be a finite number > 0, and manualReviewThreshold <= fraudCeiling (otherwise the manual-review band is unreachable). Invalid config is rejected.
Fail closed: on invalid configuration the service returns MANUAL_REVIEW with reasonCode: CONFIG_UNAVAILABLE rather than auto-approving or hard-rejecting.
Reason codes (new contract)
INVALID_PAYLOAD, INVALID_AMOUNT, INVALID_CUSTOMER, AMOUNT_EXCEEDS_FRAUD_CEILING, MANUAL_REVIEW_REQUIRED, SUSPICIOUS_CUSTOMER, CONFIG_UNAVAILABLE. VERIFIED outcomes carry no reason code. Existing codes must not be renamed/repurposed.

Security
Thresholds and overrides come only from operator/admin-set environment config — never from request payloads. A tenantId (or threshold-like field) on the invoice payload is ignored.
The per-tenant map is parsed into a Map, so prototype-pollution keys like proto cannot be resolved as tenants (covered by a test).
Tests
tests/invoiceVerification.service.test.js rewritten and expanded; src/tests/invoiceVerification.test.js updated for the new reasonCode field.
Covers config-driven thresholds, per-tenant overrides (stricter/looser/partial/unknown), reason codes, at-threshold boundaries, and invalid-config handling.
102 tests pass; coverage: config module 100%, service 95.6% (≥95% bar). eslint clean on all changed files.
Notes for reviewers
Two CI failures on this branch are pre-existing on the upstream base (ad43a78) and unrelated to this change — verified by diffing against the base:

src/metrics.js has a duplicate registerJobQueue/registerWorker declaration (lines 238 & 548).
config.envReference test: upstream's JOB_QUEUE_PERSISTENCE_ENABLED and SHUTDOWN_TIMEOUT_MS are in .env.example but undocumented.

…verrides

Move the fraud-rejection ceiling (10,000,000) and manual-review threshold
(1,000,000) out of hardcoded literals in invoiceVerification.js into
environment-backed configuration with optional per-tenant overrides.

- Add src/config/verificationThresholds.js: env-driven defaults
  (INVOICE_FRAUD_CEILING, INVOICE_MANUAL_REVIEW_THRESHOLD) plus a validated
  per-tenant override map (INVOICE_TENANT_THRESHOLDS). Defaults preserve the
  prior behavior. Overrides are parsed into a Map (prototype-pollution safe)
  and sourced only from config/admin, never request payloads.
- Emit a stable machine-readable reasonCode on every reject/manual-review
  decision so downstream handlers can branch reliably.
- Fail closed to MANUAL_REVIEW (CONFIG_UNAVAILABLE) on invalid configuration.
- Comprehensive tests for config-driven thresholds, tenant overrides,
  reason codes, at-threshold boundaries, and invalid config (config 100%,
  service 95.6% coverage).
- Document thresholds, override format, and reason codes in
  docs/configuration.md and .env.example.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@drips-wave

drips-wave Bot commented Jun 29, 2026

Copy link
Copy Markdown

@extolkom Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Make the invoice-verification fraud and manual-review thresholds configurable per tenant

1 participant