Skip to content

feat(jobs/challenges): Phase 3 — signals endpoint + 5 processors#842

Open
raymondjacobson wants to merge 1 commit into
api/challenges-phase-2from
api/challenges-phase-3
Open

feat(jobs/challenges): Phase 3 — signals endpoint + 5 processors#842
raymondjacobson wants to merge 1 commit into
api/challenges-phase-2from
api/challenges-phase-3

Conversation

@raymondjacobson
Copy link
Copy Markdown
Member

Summary

Stacked on #841 (Phase 2). Final 5 challenge processors plus the signals endpoint they consume from.

After this PR, every non-Solana challenge in apps' `challenges.json` is implemented.

New infrastructure

Migration 0205:

  • `challenge_signal_type` enum: `mobile_install`, `one_shot`, `referral`
  • `challenge_signals` table (id, type, user_id, extra JSONB, created_at, source, client_nonce)
  • UNIQUE `(type, user_id, client_nonce)` for client-replay dedupe
  • Catalog seed rows for `m`/`r`/`rv`/`rd`/`o`

Endpoint `POST /v1/challenges/signals` (api/v1_challenges_signals.go):

  • Body: `{type, user_id, extra, client_nonce?}`
  • Auth: `requireAuthMiddleware` + `requireWriteScope` (existing pattern).
  • For `mobile_install` and `referral`, the authed user must equal the target — you can only report your own install or claim your own referrer.
  • `one_shot` is currently accepted from any authed wallet; tightening to an admin allowlist is a follow-up.

Processors

ID Signal type Target Gate
`m` mobile_install `mobile_install` signal user
`o` one_shot `one_shot` signal user `extra.amount` overrides catalog; `extra.nonce` (or signal id) goes into specifier so multiple grants per user are distinct
`r` referral `referral` extra.referrer_user_id referrer NOT verified
`rv` verified_referral `referral` extra.referrer_user_id referrer IS verified
`rd` referred_signup `referral` signal user (the referred user)

`r` / `rv` / `rd` all read the same `referral` signal but checkpoint independently. One client POST → up to two outputs (one for the referrer, one for the referred).

Wire-in

5 new processors appended to `IndexChallengesJob` — 23 total now.

Test plan

6 new DB-backed tests in addition to Phase 1+2's 23:

  • `TestMobileInstall_OneRowPerUser` — boolean completion + idempotent rerun
  • `TestOneShot_ExtraAmountOverrides` — `extra.amount` overrides catalog
  • `TestReferral_NonVerifiedReferrer` — r fires, rv skipped, rd fires
  • `TestReferral_VerifiedReferrer` — r skipped, rv fires
  • `TestSignals_CheckpointAdvances` — incremental processing across runs
  • All 23 prior challenge tests still pass
  • `go build ./...` + `go vet ./...` clean

What's left after this

Only the explicitly-out-of-scope Solana challenge: `ft` (send_first_tip). Every other entry in `challenges.json` is implemented across #835, #841, and this PR.

🤖 Generated with Claude Code

Adds the last 5 challenge processors plus the infra they need.

New infra
---------

Migration 0205:
  * challenge_signal_type ENUM ('mobile_install', 'one_shot', 'referral')
  * challenge_signals table with (id, type, user_id, extra jsonb, created_at,
    source, client_nonce). UNIQUE (type, user_id, client_nonce) for client
    replay dedupe; index on (type, id) for incremental scans.
  * Catalog seed for m/r/rv/rd/o.

POST /v1/challenges/signals (api/v1_challenges_signals.go):
  * Body: {type, user_id, extra, client_nonce?}
  * Auth: requireAuthMiddleware + requireWriteScope (existing pattern).
  * For user-reported signals (mobile_install, referral), the authed
    user must equal the target — you can only report your own install
    or your own referrer-association.
  * one_shot is currently treated as admin-issued; tightening to an
    explicit admin allowlist is a follow-up.

Processors (jobs/challenges/signals.go)
---------------------------------------

  m   mobile_install   boolean per user; signal user_id == reward target
  o   one_shot         extra.amount overrides catalog; nonce in specifier
                       so the same user can receive multiple grants
  r   referral (sender)         referrer earns when NOT verified
  rv  verified_referral         referrer earns when verified
  rd  referred_signup           referred user earns once per referral signal

r/rv/rd all read the same "referral" signal type but checkpoint
independently. A single client POST therefore triggers up to two outputs
(r or rv for the sender, rd for the recipient).

Wire-in
-------

5 new processors registered in IndexChallengesJob (23 total now).

Tests
-----

6 new DB-backed tests covering each processor's happy path and gates:
  * TestMobileInstall_OneRowPerUser
  * TestOneShot_ExtraAmountOverrides
  * TestReferral_NonVerifiedReferrer  (r fires, rv skipped, rd fires)
  * TestReferral_VerifiedReferrer    (r skipped, rv fires)
  * TestSignals_CheckpointAdvances   (incremental processing across runs)

All 29 challenge tests pass (23 challenges × variety of cases); go build +
go vet clean.
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.

1 participant