Repository navigation
Add a fraud flag review workflow and mount the admin fraud router #985
Description
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 insrc/index.ts:grep -rn adminFraudRouter srcmatches only its own file.GET /api/admin/fraud/flagsandPOST /api/admin/fraud/scanare unreachable. - No review transition. The router only has
GET /flagsandPOST /scan(around lines 70–106). Yetfraud_flags.status,reviewed_byandreviewed_atexist insrc/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 withstatus = '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
- Mount
adminFraudRouterat/api/admin/fraudinsrc/index.ts, behind the existingrequireAdmin. - Review endpoint. Add
PATCH /api/admin/fraud/flags/:idwith body{ status: "confirmed" | "dismissed", note?: string }, validated with zod in.strict()mode.- Allowed transitions:
open → confirmed,open → dismissed, andconfirmed → dismissed(which needs a note). Anything else returns409 invalid_transition. - Set
reviewed_byfrom the admin's address andreviewed_at = now(). - Every transition writes
createAuditLog({ action: "fraud_flag.review", beforeState, afterState }).
- Allowed transitions:
- Stable decisions.
- Change
makeClusterKeyto a SHA-256 of the sorted members, keeping the members inevidence. - In
DrizzleFraudRepo.upsertFlags, when a user already has adismissedflag whose evidence covers the same edges, insert the new row withstatus = 'dismissed'and acarried_fromreference. Re-open it only when new edge reasons appear. Add thecarried_fromcolumn through a migration.
- Change
- Scheduled detector. Add
fraud-detectorandfraud-detector:devscripts that runsrc/workers/fraudDetector.tson its interval, with graceful shutdown. - OpenAPI and docs. Register the three admin operations in
src/openapi/registry.ts, and updatedocs/fraud-signal.mdwith 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
-
adminFraudRouteris 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 --noEmitandnpm testpass, with coverage of at least 90% on the touched files. -
npm run openapi:checkandnpm run db:check-driftpass.
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.tsRead these first:
src/routes/admin/fraud.tsclusterize,makeClusterKeyandDrizzleFraudRepoinsrc/services/fraudService.tssrc/workers/fraudDetector.tsdocs/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
aliyuHabibu commentedon Oct 18, 2026
@aliyuHabibu has applied to work on this issue as part of the Stellar Wave Program's 10th wave.
ℹ️ Repo Maintainers: To accept this application, review their application or assign @aliyuHabibu to this issue.
drips-wave commentedon Oct 19, 2026
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 🌊
grantfox-oss commentedon Oct 21, 2026
🎉 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.
drips-wave commentedon Oct 21, 2026
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.