Repository navigation
fix: resolve system settlement authorization, enum migration transactions and unbounded cache growth - #656
Merged
Conversation
Several services did not compile on dev, so the type check and build jobs could not pass: - auto-invest passed an investment without the investor wallet that createInvestment requires - document-export called an IPFS fetch helper that did not exist - invoice-refund referred to an underscore prefixed parameter by its unprefixed name - issuer-analytics used a cache API that no longer existed The lock file is regenerated as well, since it did not match package.json and npm ci refused to run.
The settlement worker passed the seller wallet as the acting wallet, falling back to the literal "system" when no wallet was recorded. The seller wallet guard then rejected every such settlement with 403 FORBIDDEN because a Stellar address can never equal "system", so automated settlement cycles failed. Automated settlement now identifies itself as a system actor and marks the request as system triggered. The seller wallet equality check is skipped for system actors while the explicit seller id check still applies, and a wallet actor that is not the seller is still rejected. The system matured trigger is recorded as the existing maturity settled lifecycle reason.
ALTER TYPE ... ADD VALUE cannot execute inside a transaction block in Postgres, and TypeORM wrapped migrations in one, so adding the rejected status failed with SQLSTATE 25001. Migrations now run in per-migration transaction mode, and every migration that adds an enum member opts out of the managed transaction. The same failure affected the notification type, failed status and admin role migrations, so they are marked non-transactional too. Each statement stays guarded so a partially applied migration can be retried.
The in-process map only dropped an entry when that exact key was read again after it expired, so keys written once and never read stayed resident for the lifetime of the process. Under real traffic that is an unbounded leak that ends in a heap exhaustion crash. The cache now caps itself at five thousand entries by default and evicts the least recently used one when full, refreshing recency on reads. Expired entries are pruned whenever a new value is written and by a background sweeper that does not hold the process open. The sweep interval and capacity are both configurable, and the cache can be closed explicitly.
… routes The schema drift check failed because several mounted routers and the readiness probe had no specification. The routes are documented now, and the unversioned alias mounts follow the same allowance as the auth and notification aliases.
|
@dotmantissa 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.
Description
This PR fixes three defects in the settlement pipeline, the migrations that change Postgres enums, and the in-process TTL cache. It also repairs the compile and schema drift failures that already existed on
dev, because the required workflows could not pass without them.Closes #628
Closes #632
Closes #633
Type of Change
Key Changes
#628 — automated settlement rejected every matured invoice
SettlementWorkerpassedinvoice.sellerWallet || "system"as the actor wallet. The service compares that value against the seller's Stellar address, and"system"can never equal an address, so every invoice without a recorded seller wallet failed with403 FORBIDDEN.src/services/settlement.service.ts: exportsSYSTEM_SETTLEMENT_ACTORandisSystemSettlementActor, accepts asystemTrigger, and skips the seller wallet equality check for system actors. The explicitsellerIdcheck still runs, so a caller cannot settle another seller's invoice by claiming to be the system.src/workers/settlement.worker.ts: settles as the system identity with thesystem_maturedtrigger instead of impersonating the seller.system_maturedhas no lifecycle reason of its own, so it is recorded as the existingmaturity_settledreason for the status history.#632 — enum migration failed with SQLSTATE 25001
ALTER TYPE ... ADD VALUEcannot run inside a transaction block, and TypeORM wrapped migrations in one.transaction = false.src/config/database.tsruns migrations ineachmode. This is required: TypeORM throwsForbiddenTransactionModeOverrideErrorwhen a migration overrides the transaction mode under the defaultallmode, so the flag alone was not enough.IF NOT EXISTS, so a partially applied migration can be retried.#633 — unbounded in-process cache
TtlCacheonly dropped an entry when that exact key was read again after expiring, so keys written once and never read stayed resident for the process lifetime.maxEntries), with least-recently-used eviction and recency refreshed on reads.unref'd so it never holds the process open.sweepIntervalMsis configurable andclose()stops the timer.sizeandpruneExpired()are exposed, andinvalidatePrefix()removes a namespace slice in-process.CI repair (pre-existing on
dev)devdid not type check or build, andnpm cirefused to install:auto-investcalledcreateInvestmentwithout the requiredinvestorWallet.document-exportcalled an IPFSfetchFilehelper that did not exist; it is now implemented against the gateway.invoice-refundreferenced an underscore-prefixed parameter by its unprefixed name.issuer-analyticsused a cache API that no longer exists.package-lock.jsonwas regenerated because it did not matchpackage.json(missingarchiver).docs/openapi.jsonnow documents the investor, portfolio, marketplace, seller, watchlist and readiness routes, and the verifier accepts the new alias mounts the same way it already accepted the auth and notification aliases.Checklist
feat:,fix:,chore:, etc.) — enforced by CI.env, or credentials committed (seeCONTRIBUTING.md)Testing
How to Test
npm run lint && npm run type-check && npm run buildnpm run test:ci(1778 tests, 1768 passed, 10 skipped)npm run verify:openapiSettlementWorker.processMaturedInvoices()now callssettleInvoicewithactorWallet: "system"andsystemTrigger: "system_matured";tests/unit/workers/settlement.worker.test.tsasserts the call shape.tests/migrations/enum-migrations-transaction.test.tsasserts each enum migration opts out of the transaction and that the data source runs ineachmode.Test Coverage
The SQL migration script (
scripts/test-migrations.sh) could not be run in this environment because no PostgreSQL server was available; the enum migration is covered by unit tests instead.Additional Notes
Each issue was implemented on its own branch (
fix/issues-628,fix/issues-632,fix/issues-633) and merged here. The CI repair lives onfix/ci-baselineand is included because the required checks were already failing ondev.For Reviewers
Please pay particular attention to the system actor bypass in
settleInvoice: the seller id check must remain the authorization boundary, and only non-wallet actors should skip the wallet equality check.