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.ts — findAutoReleaseEligible, 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.ts — delete, 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
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.
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:markDeliveredatsrc/escrow/escrow.repository.ts:212-226is the only writer ofdeliveredAt, and it setsstate: 'DELIVERED'in the same update. An escrow can never beSHIPPEDwith adeliveredAt.The tests hide it.
test/auto-release-concurrent.e2e-spec.ts:70-86builds fixtures withprisma.escrow.create({ data: { state: 'SHIPPED', deliveredAt: pastDelivery, ... } }), writing the impossible combination directly and bypassingmarkDelivered. The tests prove the worker handles a state production cannot reach.Two implementations.
src/workers/auto-release.worker.tsWorkersModulemarkAutoReleaseSubmitting, so two replicas both submitsrc/escrow/auto-release.service.tsPlaceholder signer.
src/workers/auto-release.worker.ts:14-16defaultsAUTO_RELEASE_SOURCEto the literal'GAUTORELEASE000...', which is not a valid address on any network.Where to touch
src/escrow/escrow.repository.ts—findAutoReleaseEligible, correct the state predicatesrc/workers/auto-release.worker.ts— adopt the atomic claim; replace the placeholder address with required configsrc/escrow/auto-release.service.ts— delete, after moving its claim pattern into the workertest/auto-release-concurrent.e2e-spec.tsandtest/integration/auto-release-batch.integration-spec.ts— build fixtures throughmarkDelivered, not rawcreateAcceptance criteria
DELIVEREDescrows past the windowmarkDelivered, so a test can no longer pass on a state the application cannot produceautoReleaseTxHashalready set is not selectedrun()calls submit exactly oncenpm run test:covandnpm run test:integrationpass, coverage at or above 70%Out of scope
Making the on-chain submission actually work.
ContractServicecannot submit transactions today (STELLAR_SERVERis provided asundefinedinsrc/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
nvm use), runnpm cirather thannpm install, and runnpx prisma generateafter installing. Skipping that last step makesnpm run typecheckfail withModule '"@prisma/client"' has no exported member— a missing step, not a broken checkout.bearer()fromtest/auth-helper.ts. Sending a bare Stellar address as a bearer token returns 401. See Writing Tests That Need Authentication.devand open your pull request againstdev, notmain.devis the default branch;mainis the released baseline. If you branched earlier, rebase ontodev.