Skip to content

Auto-release can never fire: eligibility query matches an impossible state #395

Description

@Omoboi-dev

Context

Auto-release has never fired. The eligibility query asks for a state combination the application cannot produce, so the worker processes zero rows on every run. Separately there are two implementations of the feature, and the one wired into the app is the weaker of the two.

Current state

The query cannot match. src/escrow/escrow.repository.ts:284-297:

findAutoReleaseEligible(referenceTime = new Date()) {
  const cutoff = new Date(referenceTime.getTime() - 48 * 60 * 60 * 1000);
  return this.prisma.escrow.findMany({
    where: {
      state: 'SHIPPED',                 // <- requires SHIPPED
      deliveredAt: { lte: cutoff },     // <- requires deliveredAt to be set
      ...

markDelivered at src/escrow/escrow.repository.ts:212-226 is the only writer of deliveredAt, and it sets state: 'DELIVERED' in the same update. An escrow can never be SHIPPED with a deliveredAt.

The tests hide it. test/auto-release-concurrent.e2e-spec.ts:70-86 builds fixtures with prisma.escrow.create({ data: { state: 'SHIPPED', deliveredAt: pastDelivery, ... } }), writing the impossible combination directly and bypassing markDelivered. The tests prove the worker handles a state production cannot reach.

Two implementations.

File Registered? Problem
src/workers/auto-release.worker.ts Yes, in WorkersModule Never calls markAutoReleaseSubmitting, so two replicas both submit
src/escrow/auto-release.service.ts No module Uses the atomic claim correctly, but passes an already-computed cutoff into a function that subtracts another 48 hours, and does not compile

Placeholder signer. src/workers/auto-release.worker.ts:14-16 defaults AUTO_RELEASE_SOURCE to the literal 'GAUTORELEASE000...', which is not a valid address on any network.

Where to touch

  • src/escrow/escrow.repository.tsfindAutoReleaseEligible, correct the state predicate
  • src/workers/auto-release.worker.ts — adopt the atomic claim; replace the placeholder address with required config
  • src/escrow/auto-release.service.tsdelete, after moving its claim pattern into the worker
  • test/auto-release-concurrent.e2e-spec.ts and test/integration/auto-release-batch.integration-spec.ts — build fixtures through markDelivered, not raw create

Acceptance criteria

  • The query selects DELIVERED escrows past the window
  • Exactly one auto-release implementation remains in the codebase
  • Startup fails with a clear message when the signer address is unset — no literal fallback
  • Test fixtures go through markDelivered, so a test can no longer pass on a state the application cannot produce
  • A test proves an escrow delivered more than 48 hours ago is selected
  • A test proves a disputed escrow is not selected
  • A test proves an escrow with autoReleaseTxHash already set is not selected
  • A concurrency test proves two simultaneous run() calls submit exactly once
  • npm run test:cov and npm run test:integration pass, coverage at or above 70%

Out of scope

Making the on-chain submission actually work. ContractService cannot submit transactions today (STELLAR_SERVER is provided as undefined in src/stellar/stellar.module.ts), so tests must continue to mock it. That is separate, blocked work.

Blocked by

None. Note this deletes src/escrow/auto-release.service.ts, which resolves one error in the TypeScript issue. Whichever merges second needs a rebase.


Before you start

  • Setup: CONTRIBUTING.md → Development Setup. Use Node 22 (nvm use), run npm ci rather than npm install, and run npx prisma generate after installing. Skipping that last step makes npm run typecheck fail with Module '"@prisma/client"' has no exported member — a missing step, not a broken checkout.
  • Tests that authenticate: use bearer() from test/auth-helper.ts. Sending a bare Stellar address as a bearer token returns 401. See Writing Tests That Need Authentication.
  • Branch from the latest dev and open your pull request against dev, not main. dev is the default branch; main is the released baseline. If you branched earlier, rebase onto dev.

Metadata

Metadata

Assignees

Labels

Stellar WaveIssues in the Stellar wave programbugSomething isn't workingcomplexity: medium150 pts. New endpoint, service method, integration test

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions