Skip to content

feat(backend): escrow funding window, README roadmap + DI guide, creation-test audit (#247, #181, #272, #301) - #304

Merged
Tybravo merged 1 commit into
SwiftChainn:mainfrom
feyisayoadetunji2-jpg:chore/issues-181-247-272-301
Oct 21, 2026
Merged

Tybravo merged 1 commit into
SwiftChainn:mainfrom
feyisayoadetunji2-jpg:chore/issues-181-247-272-301

Conversation

@feyisayoadetunji2-jpg

Copy link
Copy Markdown
Contributor

Summary

This PR resolves four tracked issues in one branch:

  • [Feature] Add an escrow funding window with automatic cancel on lapse #247 (feature, high) — deliveries created with an escrow amount now get a fundBy funding deadline and are auto-cancelled (with customer notification, a funding_window_lapsed milestone and an audit-log entry) when the window lapses; late funding of a cancelled delivery is rejected with a clear 409.
  • [Docs] Update README roadmap to reflect completed Phase 1 and Phase 2 implementations #181 (docs, low) — the README roadmap now reflects reality: every truly implemented Phase 1 / Phase 2 item is checked off with a short "Implemented" note linking to the code, while Phase 3 / Phase 4 are explicitly labelled future work.
  • [Docs] DI container and dependency wiring guide (Awilix) #272 (docs, high) — new backend/docs/dependency-injection.md teaches contributors how to register and verify services/controllers in the Awilix container (token naming, .service.ts conventions, PROXY injection, singletons, the dlqService circular-dependency pattern, and duplicate-token warnings).
  • audit-creation-test-please-close #301 (audit) — the creation-test audit found that the delivery creation suite could not even load (src/routes/delivery.routes.ts referenced an undefined validate identifier); 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 → Model layering 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 via mongodb-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

  • Schema (src/models/Delivery.ts): added fundBy (funding deadline) and cancellationReason, plus a { fundBy, status, isDeleted } index that serves the lapse scan.
  • Configuration (src/config/env.ts, .env.example): new ESCROW_FUNDING_WINDOW_SECONDS (default 86400 = 24h).
  • Creation (src/services/delivery.service.ts): when an escrow amount is configured, fundBy is stamped at creation as now + ESCROW_FUNDING_WINDOW_SECONDS; an explicit fundBy override is honoured, and deliveries without an escrow amount get no deadline.
  • Lapse handling (src/services/escrow.service.ts):
    • scanLapsedFundingWindows() cancels still-pending, non-archived deliveries whose fundBy has passed, using a conditional update that asserts the current status (safe under concurrency).
    • Each cancellation writes an AuditLog entry (action: funding_window_lapsed) and raises a new funding_window_lapsed milestone (src/models/DeliveryEvent.ts) — the existing milestone fan-out notifies the customer of the cancelled status and reaches webhooks and sockets.
    • Reject-after-cancel policy (documented): funding a cancelled delivery is rejected with 409 and an explanatory message ("…cancelled and can no longer be funded… Create a new delivery to fund it."). A fund request that arrives after fundBy but 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 in backend/docs/api/escrow.md.
  • Cron (src/jobs/escrowMonitor.ts): the escrow monitor tick now runs both scans (lock expiry + funding-window lapse) under one re-entrancy guard; runFundingWindowScan and runEscrowMonitorTick are exported for tests / on-demand triggers. Existing job startup wiring (escrowMonitorService, server.ts) is unchanged.
  • Tests (tests/escrowFundingWindow.test.ts, 16 tests): fundBy defaulting (configured / not configured / explicit override), lapse boundaries (before, at and after fundBy; 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

  • Phase 1 (auth, delivery CRUD, driver assignment, schema design, validation/error handling) and Phase 2 (Stellar integration, escrow initiation endpoints, transaction tracking, payment verification webhooks) are checked off, each with a one-line "Implemented" note linking to the implementing controllers/services/routes/models (every referenced path was verified to exist).
  • Phase 3 and Phase 4 were left unchecked and are now explicitly labelled future work — not started as a milestone, with an honest note about the partial foundations that exist.
  • No roadmap item was removed; only status and wording were corrected.

#272 — DI container & dependency wiring guide

New backend/docs/dependency-injection.md:

  1. Where the container lives (src/di/{tokens,container,index}.ts) and how it boots from src/app.ts.
  2. Container lifecycle: InjectionMode.PROXY, singleton registrations with asValue, getContainer() / createDIContainer() / resetContainer().
  3. Token naming rules mapped to the .service.ts / .controller.ts / model file-naming conventions (strip .service, add Model/Controller, one file → one token → one registration).
  4. A full worked example: add a dispatchService + dispatchController (create → token → register → resolve → override in a test).
  5. Breaking circular dependencies with the established dlqService ↔ stellarService dynamic-import pattern, plus rules for when to use it.
  6. The tests/di.container.test.ts suite and how to verify registration validity (including the registry-dump snippet).
  7. A warning section on the historic duplicate-token problem (delivery_service / escrow_service) and how to check for regressions (regression test, grep, registry diff).
  8. A PR checklist for container changes.

#301 — Creation-test audit

Audit findings and remediation:

  • Finding (critical): src/routes/delivery.routes.ts called validate(...) while the import is validateRequest, throwing ReferenceError: validate is not defined at module load. Every test that imports src/app — including the whole creation suite — failed before running a single assertion. Fixed.
  • Finding (coverage gaps): creation was only covered by three API-level cases. Added tests/delivery.creation.test.ts (8 tests): creation defaults (pending, isDeleted: false, default XLM assets, no cancellationReason), database persistence, fundBy stamping when an escrow is configured (and absence otherwise), the created milestone as seq 1, unsupported-asset and missing-field rejection, and cached-response replay for a repeated Idempotency-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 actually ready, 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 documented emitMilestone contract (milestone consumer failures are logged and isolated; the committed status transition stands).
  • src/sockets/messageQueue.ts: prettier formatting fix, so pnpm run lint is error-free repo-wide.

Files of interest

How to test locally

  1. Install and configure:
pnpm install
cp .env.example .env   # no extra services needed for the unit suites below
  1. Type-check and lint:
npx tsc --noEmit
pnpm run lint
  1. Run the suites covering this PR:
npx jest tests/escrowFundingWindow.test.ts tests/delivery.creation.test.ts \
  tests/delivery.test.ts tests/deliveryStatusTransition.test.ts \
  tests/escrow.service.test.ts tests/di.container.test.ts

Proof of Work

TypeScript + lint (both clean):

$ npx tsc --noEmit
tsc exit=0

$ pnpm run lint        # eslint . --ext .ts
✖ 86 problems (0 errors, 86 warnings)   # 0 errors; warnings pre-date this PR

New and touched suites — all green:

$ npx jest tests/escrowFundingWindow.test.ts tests/delivery.creation.test.ts \
    tests/delivery.test.ts tests/deliveryStatusTransition.test.ts \
    tests/escrow.service.test.ts tests/di.container.test.ts

PASS tests/delivery.test.ts
PASS tests/delivery.creation.test.ts
PASS tests/deliveryStatusTransition.test.ts
PASS tests/escrowFundingWindow.test.ts
PASS tests/escrow.service.test.ts
PASS tests/di.container.test.ts

Test Suites: 6 passed, 6 total
Tests:       103 passed, 103 total

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 jest now runs 1078 tests with 988 passing (58 suites green). The 14 remaining failing suites were all already broken before this PR — on main every one of them fails at import with ReferenceError: validate is not defined, which this PR fixes, after which they fail for unrelated, pre-existing reasons that predate these four issues:

  • environment-dependent fixtures (Unsupported asset or issuer: USDC because USDC_ASSET_ISSUER is unset; MongoParseError where MONGODB_URI is unset),
  • response-shape/wording drift in older tests (adminDisputes, dispute, disputeRoutes, fleet, swagger, auth.flow),
  • a @stellar/stellar-sdk/axios import interop error (integration/deliveryEta),
  • long-flaky socket/status integration suites (integration/socketLocation, deliveryStatusUpdate) — integration/socketLocation also leaks its socket server handle, which is why a full local npx jest run reports "Jest did not exit" after completing (pre-existing; this PR does not touch that suite).

CI on main has been failing for these same suites for weeks (see the run history on main), 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

  • PR targets main and links all four issues (Closes #181, Closes #247, Closes #272, Closes #301)
  • Strict Controller → Service → Model layering preserved
  • Versioned API (/api/v1/...), no breaking route changes
  • No inline mocks / hardcoded integration values — tests use a real in-process MongoDB
  • New behaviour is configuration-driven (ESCROW_FUNDING_WINDOW_SECONDS) and documented (.env.example, escrow API docs)
  • npx tsc --noEmit clean, pnpm run lint 0 errors, new/touched suites 103/103 passing

…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).
@drips-wave

drips-wave Bot commented Oct 21, 2026

Copy link
Copy Markdown

@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! 🚀

Learn more about application limits

@Tybravo
Tybravo merged commit b25258e into SwiftChainn:main Oct 21, 2026
1 check failed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants