Repository navigation
feat(backend): escrow funding window, README roadmap + DI guide, creation-test audit (#247, #181, #272, #301) - #304
Merged
Tybravo merged 1 commit intoOct 21, 2026
Conversation
…t audit (SwiftChainn#247, SwiftChainn#181, SwiftChainn#272, SwiftChainn#301) SwiftChainn#247 — Escrow funding window with automatic cancel on lapse: - Add fundBy and cancellationReason to the Delivery schema, with a { fundBy, status, isDeleted } index for the lapse scan. - Stamp fundBy at creation (creation time + ESCROW_FUNDING_WINDOW_SECONDS, default 86400s) whenever an escrow amount is configured; optional explicit override via CreateDeliveryInput.fundBy. - escrowService.scanLapsedFundingWindows cancels still-pending deliveries whose deadline passed: conditional status transition to CANCELLED with a cancellationReason, an AuditLog entry (action=funding_window_lapsed) and a funding_window_lapsed milestone that fans out to notifications/webhooks/ sockets so the customer is told. - The escrow monitor cron now runs both scans per tick (lock expiry + funding-window lapse); runFundingWindowScan/runEscrowMonitorTick are exported for tests and on-demand triggers. - Reject-after-cancel policy (documented): funding a cancelled delivery is a 409 with a clear message; a fund request arriving after fundBy cancels the delivery inline before rejecting, so deliveries can no longer sit in FUNDED-pending limbo. Documented in backend/docs/api/escrow.md. SwiftChainn#181 — README roadmap reflects reality: - Check off the implemented Phase 1 and Phase 2 items, each with a short "Implemented" note linking to the implementing controllers/services/ routes/models. Phase 3 and Phase 4 stay unchecked and are explicitly labelled future work. SwiftChainn#272 — DI container guide: - Add backend/docs/dependency-injection.md covering the Awilix container lifecycle (PROXY injection, singleton registrations, getContainer/ resetContainer), token naming rules mapped to the .service.ts conventions, a full worked example adding a service + controller, the dlqService dynamic-import pattern for circular dependencies, the di.container test and how to verify registration validity, plus a warning section on the historic duplicate-token problem and how to check for regressions. SwiftChainn#301 — Creation-test audit: - Audit finding: the creation test suite could not even load because src/routes/delivery.routes.ts referenced an undefined `validate` identifier (the import is validateRequest), which broke every test that imports src/app. Fixed. - Add tests/delivery.creation.test.ts covering creation defaults (status, assets), the fundBy deadline, the `created` milestone, unsupported-asset validation and idempotent replay of a repeated Idempotency-Key. Supporting fixes needed to make the suites runnable/verifiable: - idempotency store: only use Redis when its connection is actually ready, so an unavailable Redis falls back to MongoDB immediately instead of stalling each request ~11s and leaving a reconnect handle that prevented Jest from exiting. - tests/deliveryStatusTransition.test.ts: repair a stray invalid-character line that broke compilation, and align the notification-failure test with the documented emitMilestone contract (consumer failures are logged and isolated; the committed transition stands). - sockets/messageQueue.ts: prettier formatting fix (repo-wide lint is now error-free).
|
@feyisayoadetunji2-jpg 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.
Summary
This PR resolves four tracked issues in one branch:
fundByfunding deadline and are auto-cancelled (with customer notification, afunding_window_lapsedmilestone and an audit-log entry) when the window lapses; late funding of a cancelled delivery is rejected with a clear409.backend/docs/dependency-injection.mdteaches contributors how to register and verify services/controllers in the Awilix container (token naming,.service.tsconventions, PROXY injection, singletons, thedlqServicecircular-dependency pattern, and duplicate-token warnings).src/routes/delivery.routes.tsreferenced an undefinedvalidateidentifier); that is fixed, and the creation path now has proper coverage for defaults, the funding deadline, milestone emission, asset validation and idempotent replay.The strict
Controller → Service → Modellayering is preserved throughout, all API surfaces stay versioned under/api/v1/..., no inline mocks or hardcoded integration values were introduced (tests run against a real in-process MongoDB viamongodb-memory-server), and the new behaviour is driven by configuration (ESCROW_FUNDING_WINDOW_SECONDS), not hardcoding.Related Issue
Closes #181
Closes #247
Closes #272
Closes #301
What I changed
#247 — Escrow funding window with automatic cancel on lapse
src/models/Delivery.ts): addedfundBy(funding deadline) andcancellationReason, plus a{ fundBy, status, isDeleted }index that serves the lapse scan.src/config/env.ts,.env.example): newESCROW_FUNDING_WINDOW_SECONDS(default86400= 24h).src/services/delivery.service.ts): when an escrow amount is configured,fundByis stamped at creation asnow + ESCROW_FUNDING_WINDOW_SECONDS; an explicitfundByoverride is honoured, and deliveries without an escrow amount get no deadline.src/services/escrow.service.ts):scanLapsedFundingWindows()cancels still-pending, non-archived deliveries whosefundByhas passed, using a conditional update that asserts the current status (safe under concurrency).AuditLogentry (action: funding_window_lapsed) and raises a newfunding_window_lapsedmilestone (src/models/DeliveryEvent.ts) — the existing milestone fan-out notifies the customer of thecancelledstatus and reaches webhooks and sockets.409and an explanatory message ("…cancelled and can no longer be funded… Create a new delivery to fund it."). A fund request that arrives afterfundBybut before the next monitor tick cancels the delivery inline first and then rejects, so the "FUNDED-pending limbo" state can never outlive a fund request. Policy and behaviour are documented inbackend/docs/api/escrow.md.src/jobs/escrowMonitor.ts): the escrow monitor tick now runs both scans (lock expiry + funding-window lapse) under one re-entrancy guard;runFundingWindowScanandrunEscrowMonitorTickare exported for tests / on-demand triggers. Existing job startup wiring (escrowMonitorService,server.ts) is unchanged.tests/escrowFundingWindow.test.ts, 16 tests):fundBydefaulting (configured / not configured / explicit override), lapse boundaries (before, at and afterfundBy; funded and archived deliveries are never cancelled; idempotent re-scan), milestone + audit-log assertions, reject-after-cancel (409, no escrow created, delivery stays cancelled), inline lapse on a late fund request, and the cron entry points.#181 — README roadmap now matches the implementation
#272 — DI container & dependency wiring guide
New
backend/docs/dependency-injection.md:src/di/{tokens,container,index}.ts) and how it boots fromsrc/app.ts.InjectionMode.PROXY, singleton registrations withasValue,getContainer()/createDIContainer()/resetContainer()..service.ts/.controller.ts/ model file-naming conventions (strip.service, addModel/Controller, one file → one token → one registration).dispatchService+dispatchController(create → token → register → resolve → override in a test).dlqService↔stellarServicedynamic-import pattern, plus rules for when to use it.tests/di.container.test.tssuite and how to verify registration validity (including the registry-dump snippet).delivery_service/escrow_service) and how to check for regressions (regression test, grep, registry diff).#301 — Creation-test audit
Audit findings and remediation:
src/routes/delivery.routes.tscalledvalidate(...)while the import isvalidateRequest, throwingReferenceError: validate is not definedat module load. Every test that importssrc/app— including the whole creation suite — failed before running a single assertion. Fixed.tests/delivery.creation.test.ts(8 tests): creation defaults (pending,isDeleted: false, defaultXLMassets, nocancellationReason), database persistence,fundBystamping when an escrow is configured (and absence otherwise), thecreatedmilestone asseq 1, unsupported-asset and missing-field rejection, and cached-response replay for a repeatedIdempotency-Key.Supporting fixes required to make the suites runnable and verifiable
src/services/idempotency.service.ts: the idempotency store now only uses Redis when its connection is actuallyready, otherwise it falls back to MongoDB immediately. Previously an unavailable Redis made every idempotent request block ~11s while ioredis retried and then left an endless reconnect handle that prevented the Jest runner from exiting — the documented "Redis when available, MongoDB otherwise" contract now actually holds.tests/deliveryStatusTransition.test.ts: repaired a stray invalid-character line that broke compilation, and aligned one stale assertion with the documentedemitMilestonecontract (milestone consumer failures are logged and isolated; the committed status transition stands).src/sockets/messageQueue.ts: prettier formatting fix, sopnpm run lintis error-free repo-wide.Files of interest
src/services/escrow.service.ts— funding-window guard, lapse cancellation, scan, audit logsrc/jobs/escrowMonitor.ts— cron tick now runs both scanssrc/models/Delivery.ts,src/models/DeliveryEvent.ts—fundBy/cancellationReason,funding_window_lapsedmilestonesrc/services/delivery.service.ts—fundBydefaulting at creationsrc/config/env.ts,.env.example—ESCROW_FUNDING_WINDOW_SECONDSsrc/services/idempotency.service.ts— Redis-readiness fallbacksrc/routes/delivery.routes.ts—validate→validateRequestfix (audit-creation-test-please-close #301)tests/escrowFundingWindow.test.ts,tests/delivery.creation.test.ts— new suitesREADME.md— roadmap ([Docs] Update README roadmap to reflect completed Phase 1 and Phase 2 implementations #181)backend/docs/dependency-injection.md— DI guide ([Docs] DI container and dependency wiring guide (Awilix) #272)backend/docs/api/escrow.md— funding-window policy documented ([Feature] Add an escrow funding window with automatic cancel on lapse #247)How to test locally
pnpm install cp .env.example .env # no extra services needed for the unit suites belowProof of Work
TypeScript + lint (both clean):
New and touched suites — all green:
Highlights: 16/16 funding-window tests (lapse boundaries, reject-after-cancel, milestone + audit log, cron entry points), 8/8 creation-audit tests, 21/21 delivery API tests (the creation suite previously could not load at all), 16/16 DI container tests.
Full-suite context (pre-existing failures are unrelated to this PR):
npx jestnow runs 1078 tests with 988 passing (58 suites green). The 14 remaining failing suites were all already broken before this PR — onmainevery one of them fails at import withReferenceError: validate is not defined, which this PR fixes, after which they fail for unrelated, pre-existing reasons that predate these four issues:Unsupported asset or issuer: USDCbecauseUSDC_ASSET_ISSUERis unset;MongoParseErrorwhereMONGODB_URIis unset),adminDisputes,dispute,disputeRoutes,fleet,swagger,auth.flow),@stellar/stellar-sdk/axios import interop error (integration/deliveryEta),integration/socketLocation,deliveryStatusUpdate) —integration/socketLocationalso leaks its socket server handle, which is why a full localnpx jestrun reports "Jest did not exit" after completing (pre-existing; this PR does not touch that suite).CI on
mainhas been failing for these same suites for weeks (see the run history onmain), so they are deliberately left out of this PR's scope to keep the change reviewable and conflict-free for the other open PRs.Checklist
mainand links all four issues (Closes #181,Closes #247,Closes #272,Closes #301)/api/v1/...), no breaking route changesESCROW_FUNDING_WINDOW_SECONDS) and documented (.env.example, escrow API docs)npx tsc --noEmitclean,pnpm run lint0 errors, new/touched suites 103/103 passing