Status: ✅ Complete
Branch: feat/weight-drift-audit-job
Commit: a092919 - Weight drift audit system with weekly job, approval gate, and audit trail
Severity: Medium | Area: backend/ops
Weights drift from policy without automation. Fix direction: Scheduled job + audit log + manual approval gate.
- Location: src/jobs/weightDriftAuditJob.ts
- Schedule: Every Monday at 00:00 UTC (configurable)
- Emission: Weekly diff report with per-currency analysis
- Email Notification: Sent to admin distribution list with drift summary
- Location: src/services/reserve/WeightDriftAuditService.ts
- Calculates: Actual weight vs. policy (basket config target)
- Threshold: 2% drift triggers audit record
- Per-Currency: Policy %, Actual %, Drift %, Recommendations
- Status: All audits created with "pending" status
- Admin Review: Required before any weight adjustments
- Workflow: Approve, Reject, or Hold for decision
- Tracked: All approvals logged to audit trail
- Events Logged:
WEIGHT_DRIFT_AUDIT_CREATED- New audit generatedWEIGHT_DRIFT_AUDIT_APPROVED- Admin approval recordedWEIGHT_DRIFT_AUDIT_REJECTED- Admin rejection recorded
- Compliance: Full timestamp, actor, and change history
GET /v1/admin/weight-drift-audits- List with filtering & paginationGET /v1/admin/weight-drift-audits/{id}- Single audit detailsPOST /v1/admin/weight-drift-audits- Manual audit triggerPOST /v1/admin/weight-drift-audits/{id}/approve- Approve auditPOST /v1/admin/weight-drift-audits/{id}/reject- Reject audit
| Criterion | Status | Evidence |
|---|---|---|
| Weekly job emits diff report | ✅ | weightDriftAuditJob.ts runs every Monday, generates detailed report |
| Per-currency analysis | ✅ | WeightDriftAuditService.calculateDriftReport() with thresholds |
| Audit stored pending approval | ✅ | weight_drift_audits table with status=pending |
| Manual approval gate | ✅ | API endpoints for approve/reject workflow |
| Audit log entries | ✅ | Integrated with auditService for all lifecycle events |
| Scheduled automation | ✅ | Job started in src/index.ts at app bootstrap |
| Email notifications | ✅ | Sends to ADMIN_NOTIFICATION_EMAIL after each run |
| File | Purpose | LOC |
|---|---|---|
docs/WEIGHT_DRIFT_AUDIT.md |
Comprehensive documentation | 363 |
prisma/migrations/20260427000000_add_weight_drift_audit/migration.sql |
Database schema | 42 |
src/controllers/weightDriftAuditController.ts |
API request handlers | 291 |
src/jobs/weightDriftAuditJob.ts |
Weekly scheduler | 230 |
src/routes/weightDriftAuditRoutes.ts |
Route definitions | 44 |
src/services/reserve/WeightDriftAuditService.ts |
Core business logic | 366 |
tests/weightDriftAudit.test.ts |
Unit tests | 333 |
Total New Code: 1,669 lines
| File | Changes |
|---|---|
src/routes/index.ts |
Added import and route registration |
src/index.ts |
Added scheduler startup at boot |
Stores audit records with drift analysis and approval status.
Key Fields:
id(UUID) - Primary keyaudit_period_start/end(timestamp) - Analysis windowtotal_currencies(int) - Basket sizecurrencies_exceeding_threshold(int) - Problem countmax_drift_percent(decimal) - Maximum drift observedstatus(varchar) - pending | approved | rejecteddiff_report(jsonb) - Full analysis snapshotcreated_by/approved_by(uuid) - Admin audit trailapproval_notes(text) - Sign-off message
Snapshot of per-currency drift for each audit.
Key Fields:
id(UUID) - Primary keyaudit_id(UUID) - FK to auditcurrency(varchar) - 3-letter codepolicy_weight(decimal) - Target from basket configactual_weight(decimal) - Calculated from reservesdrift_percent(decimal) - Differenceexceeds_threshold(boolean) - Flag for > 2%recommendation(text) - Remediation guidance
# Audit scheduling
WEIGHT_DRIFT_AUDIT_INTERVAL_DAYS=7 # Default: weekly
WEIGHT_DRIFT_AUDIT_RUN_ON_STARTUP=false # Auto-run at boot
# Notifications
ADMIN_NOTIFICATION_EMAIL=ops@acbu.com # Email list for alertsDrift Threshold: 2% (hardcoded in service)
- Currencies with |drift| > 2% marked as "exceeds_threshold"
- Recommendations generated for all drifts > 0.5%
-
Apply migration:
npx prisma migrate deploy
-
Set environment variables:
export ADMIN_NOTIFICATION_EMAIL=ops-team@acbu.com export WEIGHT_DRIFT_AUDIT_RUN_ON_STARTUP=false
-
Deploy code from branch
feat/weight-drift-audit-job -
Verify startup:
- Check logs for: "Weight drift audit scheduler started"
- Next run should be scheduled for upcoming Monday 00:00 UTC
-
Test manual trigger:
curl -X POST http://localhost:3000/v1/admin/weight-drift-audits \ -H "Authorization: Bearer $ADMIN_KEY" \ -H "Content-Type: application/json"
Unit Tests: tests/weightDriftAudit.test.ts
npm test -- tests/weightDriftAudit.test.tsCoverage:
- Drift calculation accuracy (various weight scenarios)
- Audit creation with DB persistence
- Approval/rejection workflows
- Pagination and filtering
- Audit log entry generation
- Email notification triggers
Manual Testing:
- Create audit via API → verify "pending" status
- List audits → confirm pagination works
- Approve audit → check audit trail logged
- Reject audit → verify reason recorded
- BasketService - Policy weights from active basket config
- ReserveTracker - Actual weights from on-chain reserves
- AuditService - Logging of all policy changes
- NotificationService - Email alerts to admins
// src/services/reserve/index.ts
export { weightDriftAuditService } from "./WeightDriftAuditService";- Monday 00:00 UTC: Job runs
calculateDriftReport() - USD weight drifted to 42% (policy: 40%, drift: +2%)
- Audit created with
status=pending, UUID:audit-123 - Email sent to ops team: "USD overweight by 2.00%"
- Admins review via API:
GET /v1/admin/weight-drift-audits/audit-123
- Admin reviews drift report and market conditions
- Approves:
POST /v1/admin/weight-drift-audits/audit-123/approve - Request body:
{ "approvalNotes": "Within acceptable range, no action needed" } - Status updates to
approved - Event logged:
WEIGHT_DRIFT_AUDIT_APPROVEDwith timestamp and admin ID
- Monday 00:00 UTC: New audit generated
- NGN weight now 25% (policy: 30%, drift: -5%)
- Audit created as pending
- Admin reviews and decides to wait for DAO vote
- Rejects:
POST /v1/admin/weight-drift-audits/audit-124/reject - Request body:
{ "reason": "Awaiting Q2 DAO vote on rebalancing" } - Status updates to
rejected - Event logged:
WEIGHT_DRIFT_AUDIT_REJECTEDwith reason
Logs to Watch:
✓ "Weight drift audit scheduler started"
✓ "Weight drift report calculated"
✓ "Weight drift audit created"
✓ "Weight drift audit approved" or "rejected"
✓ "Weight drift audit email sent"
✗ "Weight drift audit job failed" - Needs investigation
✗ "Failed to send weight drift audit email" - Check email config
Metrics to Track:
- Audit frequency (should be weekly)
- Currencies exceeding threshold (trend over time)
- Max drift percentage (monitor for volatility)
- Approval turnaround time (time from creation to approval)
- Rejection rate (indicates policy issues if high)
- Auto-approval rules - Approve low-drift audits automatically
- Per-currency thresholds - Different thresholds by currency liquidity
- Rebalancing instructions - Auto-generate swaps from approved audits
- Dashboard UI - Visual drift monitoring interface
- Historical analytics - Trend charts and reports
- External alerts - PagerDuty/Slack integration for critical drift
- Drift prediction - ML-based forecasting of future drift
- Policy updates - Auto-suggest weight adjustments based on market
Check:
- Logs for scheduler startup
- System clock in UTC timezone
- PostgreSQL and RabbitMQ connectivity
Check:
- Active basket config has correct weights
- Reserve tracker pulling on-chain balances
- All currencies have reserve entries
Check:
ADMIN_NOTIFICATION_EMAILenvironment variable set- Email service credentials valid
- Network connectivity to email provider
Documentation:
- WEIGHT_DRIFT_AUDIT.md - Full architecture guide
- WeightDriftAuditService API
- weightDriftAuditJob Flow
Related:
- BasketService - Policy weights
- ReserveTracker - Actual weights
- RebalancingEngine - Drift calculations
- AuditService - Compliance logging
Implementation Date: 2026-04-27
Implemented By: GitHub Copilot
Branch: feat/weight-drift-audit-job
Status: Ready for PR Review & Merge