Skip to content

Add a fraud flag review workflow and mount the admin fraud router #985

Description

@greatest0fallt1me

Summary

The fraud detector persists fraud_flags with a status column, but there is no way to review a flag. The admin router that lists flags is not mounted either, and the detector worker is never started. Each scan re-keys a cluster when it gains a member, so earlier review decisions are lost. This issue mounts the router, adds audited review transitions, keeps decisions stable across cluster growth, and runs the detector on a schedule.

Background and evidence

  • The router is not mounted. createAdminFraudRouter/adminFraudRouter (src/routes/admin/fraud.ts) is not referenced in src/index.ts: grep -rn adminFraudRouter src matches only its own file. GET /api/admin/fraud/flags and POST /api/admin/fraud/scan are unreachable.
  • No review transition. The router only has GET /flags and POST /scan (around lines 70–106). Yet fraud_flags.status, reviewed_by and reviewed_at exist in src/db/schema.ts (around lines 223–253).
  • Cluster keys drift. makeClusterKey (src/services/fraudService.ts, line 225) is the |-joined sorted member list. When one address joins a cluster, every member gets a new (cluster_key, user_id) row with status = 'open', which brings back flags an admin already dismissed.
  • The detector never runs. fraudDetectorWorker (src/workers/fraudDetector.ts, line 89) is never started by the API or by a package script.
  • Unbounded keys. For large clusters the key grows without limit (56 characters per member).

Scope

In scope

  1. Mount adminFraudRouter at /api/admin/fraud in src/index.ts, behind the existing requireAdmin.
  2. Review endpoint. Add PATCH /api/admin/fraud/flags/:id with body { status: "confirmed" | "dismissed", note?: string }, validated with zod in .strict() mode.
    • Allowed transitions: open → confirmed, open → dismissed, and confirmed → dismissed (which needs a note). Anything else returns 409 invalid_transition.
    • Set reviewed_by from the admin's address and reviewed_at = now().
    • Every transition writes createAuditLog({ action: "fraud_flag.review", beforeState, afterState }).
  3. Stable decisions.
    • Change makeClusterKey to a SHA-256 of the sorted members, keeping the members in evidence.
    • In DrizzleFraudRepo.upsertFlags, when a user already has a dismissed flag whose evidence covers the same edges, insert the new row with status = 'dismissed' and a carried_from reference. Re-open it only when new edge reasons appear. Add the carried_from column through a migration.
  4. Scheduled detector. Add fraud-detector and fraud-detector:dev scripts that run src/workers/fraudDetector.ts on its interval, with graceful shutdown.
  5. OpenAPI and docs. Register the three admin operations in src/openapi/registry.ts, and update docs/fraud-signal.md with the review states.

Out of scope

  • Enforcing holds on claims (a separate issue).
  • Changes to the detection heuristics or edge weights.

Design constraints

  • Authorization. Only admins may call these endpoints. Reviewer identity comes from the verified JWT, never from the request body.
  • Concurrency. Transitions use UPDATE … WHERE id = $1 AND status = $expected RETURNING *, so two admins reviewing at once cannot both win.
  • Idempotent scans. Re-running a scan over unchanged data writes no new rows and never changes a reviewed status.
  • Rate limiting. Reuse the router's limiter and key it on the verified admin identity (see the rate-limiter keying issue).

Test plan

Extend tests/adminFraud.test.ts and tests/fraudService.test.ts.

# Scenario Expected
1 GET /api/admin/fraud/flags through createApp() 200 for an admin; 401/403 otherwise (proves it is mounted)
2 open → confirmed 200; reviewed_by/reviewed_at set; audit row written
3 dismissed → confirmed 409 invalid_transition
4 confirmed → dismissed without a note 400 validation_error
5 Two concurrent reviews of the same flag one 200, one 409
6 Cluster grows by one member after a dismissal; edges unchanged for existing members existing members' new rows are dismissed with carried_from
7 Cluster grows with a new edge reason the affected member's flag is re-opened
8 Cluster key for 1,000 members fixed-length hash
9 Scan re-run over unchanged data no new rows; statuses unchanged

Regression proof. Rows 1 and 6 must fail on current main (router not mounted; dismissal not carried) and pass after your change.

Acceptance criteria

  • adminFraudRouter is mounted, and the PATCH endpoint exists with audited transitions.
  • Every row of the test plan passes: npm test -- tests/adminFraud.test.ts tests/fraudService.test.ts tests/fraudDetector.test.ts.
  • npm run lint, npx tsc -p tsconfig.json --noEmit and npm test pass, with coverage of at least 90% on the touched files.
  • npm run openapi:check and npm run db:check-drift pass.

Getting started

git checkout -b feat/fraud-flag-review
npm ci
docker compose up -d db && npm run db:migrate
npm test -- tests/adminFraud.test.ts tests/fraudService.test.ts

Read these first:

  • src/routes/admin/fraud.ts
  • clusterize, makeClusterKey and DrizzleFraudRepo in src/services/fraudService.ts
  • src/workers/fraudDetector.ts
  • docs/fraud-signal.md

PR requirements

  • Link this issue (Closes #<n>).
  • Include the transition table in the PR description.
  • Paste the test output and the coverage summary.
  • Example commit: feat(fraud): mount admin router and add audited flag review transitions.

Priority rationale

Medium. The work is contained to the fraud admin surface and its repository. It adds a real workflow with concurrency-safe transitions, auditability and stable cluster identity. Without it, fraud findings cannot be acted on.

Activity

  1. aliyuHabibu commented on Oct 18, 2026

    @aliyuHabibu
    Contributor

    @aliyuHabibu has applied to work on this issue as part of the Stellar Wave Program's 10th wave.

    Hi maintainer, i reviewed the issue and this is exactly the type of work i enjoy, i can deliver a clean, well-tested PR promptly, please assign it to me.

    ℹ️ Repo Maintainers: To accept this application, review their application or assign @aliyuHabibu to this issue.

  2. drips-wave commented on Oct 19, 2026

    @drips-wave

    Congratulations, @aliyuHabibu! 🎉 Your application was accepted by the repo's maintainers, and the issue is due on October 22, 2026.

    🧑‍💻 @aliyuHabibu: Please resolve the issue such that the repo's maintainers have enough time to review your contribution before the due date. You'll earn Points for completing the issue on-time, which will make you eligible for a share of the Stellar Wave Program's reward pool.

    Warning

    When opening a PR, please link it to this issue to ensure it gets tracked accurately. Points are awarded when this issue is marked as completed by the maintainer.

    🤠 Repo maintainers: Please keep an eye on the contributor's progress and review their work before the due date. You can manage this issue, including adjusting its complexity and points, here.

    🌊 Happy Wave 🌊

  3. grantfox-oss commented on Oct 21, 2026

    @grantfox-oss

    🎉 This issue has been marked as completed on GrantFox!

    @aliyuHabibu's PR #997 was approved and merged by @greatest0fallt1me.

    🏆 @aliyuHabibu: You earned 40 FoxPoints for this contribution! Your current tier: Builder (1,262 total points). Track your full progress on GrantFox.

    👏 Great work, @aliyuHabibu! Keep contributing to Predictify-org.

  4. drips-wave commented on Oct 21, 2026

    @drips-wave

    This issue has been completed by @aliyuHabibu as part of the Stellar Wave Program's 10th Wave 🥳

    😎 @aliyuHabibu: You earned 100 Points for completing this issue! Once the Wave's rewards are finalized, you'll be eligible for a percentage of the Wave's reward pool based on the percentage of total points you've earned. Learn more here. You can also Leave a review to share your experience working on this issue.

    🧑‍💻 Repo maintainers: How'd the contributor do? Leave a review to share your experience working with them.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions