feat: make invoice-verification thresholds configurable with tenant o…#542
Open
extolkom wants to merge 1 commit into
Open
feat: make invoice-verification thresholds configurable with tenant o…#542extolkom wants to merge 1 commit into
extolkom wants to merge 1 commit into
Conversation
…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>
|
@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! 🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.