Skip to content

feat: deferred distribution scheduling with idempotent dispatch - #897

Merged
thlpkee20-wq merged 2 commits into
RevoraOrg:masterfrom
BALLUCK004:feat/deferred-distribution-schedule
Aug 31, 2026
Merged

feat: deferred distribution scheduling with idempotent dispatch#897
thlpkee20-wq merged 2 commits into
RevoraOrg:masterfrom
BALLUCK004:feat/deferred-distribution-schedule

Conversation

@BALLUCK004

Copy link
Copy Markdown

Closes #871

Summary

Adds deferred distribution scheduling: operators can queue a specific distribution run at a future settlement window and the DistributionScheduler honors it idempotently.

What was built

  • Migration src/db/migrations/021_create_scheduled_distributions.sql – new scheduled_distributions table with (offering_id, period_id, run_at, status) and a UNIQUE (offering_id, period_id) idempotency key, plus a partial index for due rows and a partial index for per-offering listing.
  • ScheduledDistributionRepository (src/db/repositories/scheduledDistributionRepository.ts) – create (409 on duplicate), findDueScheduledDistributions(now, leaseMs, limit) (due + stale-processing reclaim), atomic claimScheduledDistribution, markCompleted/markFailed/markCancelled, findById/findByOffering/findAll.
  • DistributionScheduler.processScheduledDistributions(now?) (src/services/distributionScheduler.ts) – picks due rows, claims atomically, dispatches through DistributionEngine.distribute(offering_id, period, amount) with the stored snapshot boundary (period_start/period_end, falling back to run_at). Failure is sanitized to Distribution failed: <CLASS> and persisted on the row; per-row failure never aborts the tick. Configured via optional scheduledDistributionRepo / staleLeaseMs options (source-compatible; safe no-op when not configured).
  • Admin endpoints (src/routes/distributions.ts, admin-only):
    • POST /distributions/schedule – enqueue (201; 400 validation; 403 non-admin; 404 unknown offering; 409 duplicate)
    • GET /distributions/schedule – list (optional offering_id filter)
    • DELETE /distributions/schedule/:id – cancel a pending run (200; 404 if not cancellable)
  • Docs docs/deferred-distribution-scheduling.md – data model, scheduler semantics, idempotency-across-restart rationale, endpoint contracts, security/failure modes.

Acceptance criteria mapping

  • Implemented across distributionScheduler.ts + distributionEngine.ts – scheduler drives the engine with the queued snapshot boundary and amount; engine unchanged (existing idempotency findRunByParams short-circuit is what makes restart re-runs safe).
  • Security / authorization – all queue endpoints require admin role (403 otherwise); error_message only stores sanitized failure class, never raw DB/provider errors.
  • Failure / concurrency / restart behavior – atomic claim (UPDATE ... WHERE status='scheduled'), 15-min lease reclaim for crashed schedulers, in-process skip of already-claimed rows, per-row isolation; completed rows are skipped on backfill after restart.
  • Regression coverage – empty due set, duplicate enqueue (409), invalid/zero amounts, past-dated run_at, cancelled/completed not re-picked, missing repo no-op, mark-failed throw, custom lease.
  • Compatibility – purely additive: new table, new optional constructor options, new endpoints; existing scheduler API untouched.

Validation

  • src/db/repositories/scheduledDistributionRepository.test.ts – 17 tests, 100% statements/branches/functions/lines.
  • src/services/distributionScheduler.test.ts – 8 new processScheduledDistributions tests pass.
  • src/routes/__tests__/distributions.test.ts – 26 tests pass (13 new).
  • New repository added to jest.config.js collectCoverageFrom.
  • tsc --noEmit reports no errors in the changed files.

Note: the repo has pre-existing failures unrelated to this change, reproduced on master without this PR: 60 failing tests in src/services/distributionScheduler.test.ts (un-imported validateCronSyntax/STELLAR_MAINTENANCE_WINDOWS) and 6 in src/services/__tests__/distributionScheduler.test.ts (private-member access), plus a repo-wide no-explicit-any lint backlog. Targeted suites for this change are green.

Migration: apply with npm run migrate (filename-unique, tracked in schema_version).

Add a deferred distribution queue so operators can schedule a specific
distribution run for a future settlement window and have the scheduler
honor it idempotently.

- Add scheduled_distributions table (migration 021) with
  (offering_id, period_id) UNIQUE idempotency key and a partial index on
  due (run_at, status) rows plus stale processing reclaim.
- Add ScheduledDistributionRepository: create, due/lease query, atomic
  claim, complete/fail/cancel, lookups (100% coverage).
- Extend DistributionScheduler with processScheduledDistributions(now?)
  that claims due (and stale-processing) rows and dispatches them through
  DistributionEngine with the stored snapshot boundary. Completed rows
  are skipped across restarts; stale processing rows are reclaimed after
  a lease; duplicate enqueue is rejected at the table level.
- Add admin-only endpoints: POST /distributions/schedule (enqueue, 409 on
  duplicate), GET /distributions/schedule (list), and
  DELETE /distributions/schedule/:id (cancel pending runs).
- Document security/failure semantics and validation in
  docs/deferred-distribution-scheduling.md; track the new repository in
  jest coverage.

Closes RevoraOrg#871
@drips-wave

drips-wave Bot commented Aug 30, 2026

Copy link
Copy Markdown

@BALLUCK004 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

@thlpkee20-wq
thlpkee20-wq merged commit a0f64c6 into RevoraOrg:master Aug 31, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add deferred distribution scheduling so payouts can be queued for a future settlement window

2 participants