Verifluence — Implemented Use Cases
Documents the currently working use cases derived from API handlers, DB schema, and frontend pages. Last updated: June 2026.
Actors
| Actor | Description |
|---|---|
| Public | Unauthenticated visitor |
| Streamer | Authenticated content creator |
| Operator | Authenticated casino / sportsbook company |
| Admin | Platform administrator (managed via separate admin app + impersonation tokens) |
UC-1 · Operator Onboarding
UC-1.1 · Register via Invitation
Actor: Operator Preconditions: Admin has created an Invitation record and emailed the code to the operator.
Flow:
- Operator opens
/entry?invitation=CODE. - System validates the invitation code (status =
pending). - Operator accepts the Terms of Service + Privacy Policy consent. (The display name comes from the invitation — it is not entered here.)
- System redeems the code: creates an
operatorsrow (status =prepared), marks the invitationused, and signs the operator into the session. The display name is taken from the invitation. - Operator is redirected to
/operator/onboardingto complete their profile (see UC-1.4).
Postconditions: Operator account exists with status prepared, then runs the onboarding wizard. Admin must set status published to grant full access (e.g. campaign creation).
⚠️ Known gap: the ToS/Privacy consent accepted on
/entryis not persisted on the operator. There is no consent column onoperators;consent_versionis only written tooperator_wallets, and only when a wallet is later connected (UC-1.2). Redeem stamps nothing at sign-up time.
| Test | Assertion |
|---|---|
| — | not yet automated — requires admin-seeded invitations row |
UC-1.2 · Connect Operator Wallet
Actor: Operator Preconditions: Operator is authenticated. MetaMask or compatible EVM wallet installed in browser.
Flow:
- Operator opens
/operator/wallet. - Operator clicks "Connect Wallet"; MetaMask prompts for signature.
- System records
operator_walletsrow with wallet address and current consent version. - Multiple wallets can be connected; each is stored independently.
Postconditions: Wallet address available for on-chain campaign funding.
| Test | Assertion |
|---|---|
| — | not yet automated — on-chain / MetaMask, not API-testable |
UC-1.3 · Operator Sign In
Actor: Operator Preconditions: Operator account exists.
Flow:
- Operator visits any
/operator/*page. - If not authenticated, system redirects to
/signin2(which itself redirects to the live/signinpage). - Operator enters registered email; system sends a 4-digit PIN via email.
- Operator enters PIN; session cookie is set.
- Operator is redirected to original destination or
/operator/account.
| Test | Assertion |
|---|---|
| TC-AUTH-01 | Happy path — PIN sent and verified, session established |
→ Product Guide: Registration & Onboarding · Wallet
UC-1.4 · Complete Operator Onboarding
Actor: Operator Preconditions: Operator authenticated (just redeemed an invitation via UC-1.1, or resuming a partially-completed onboarding).
Flow: A 2-step wizard at /operator/onboarding. It is resume-aware — re-entry derives the starting step from saved operator data, so closing the tab mid-flow returns the operator to where they left off.
- Step 1 — Company information. Company name, operator brand, website, jurisdiction, company representative, contact email →
PUT /api/operators/:slug(persistslegal_name,name,website_url,jurisdiction,contact_name,contact_email). - Step 2 — Brand profile. Logo + cover image uploads, about, streaming categories, target regions, languages, average monthly campaigns → logo/cover upload endpoints +
PUT /api/operators/:slug. - On completion the operator is redirected to
/operator/campaigns.
Postconditions: Operator company + brand profile populated. (An earlier payout/wallet onboarding step was removed — wallet connection is now a separate action, UC-1.2.)
| Test | Assertion |
|---|---|
| — | not yet automated |
UC-2 · Campaign Management (Operator)
UC-2.1 · Create Campaign
Actor: Operator Preconditions: Operator authenticated and account status published.
Flow:
- Operator opens
/operator/campaignsand clicks "New Campaign". - Multi-step form collects:
- Name, category (
casino/sportsbook/poker/prediction_markets), description - Dates, timezone, schedule type + time slots
- Budget cap (USDC), max payout per stream
- Minimum follower, viewer, and monthly hours requirements
- Platforms, GEOs, languages
- Payout type (
manual/automatic) - Minimum terms (the five negotiable deal-term floors), brief summary, deliverables, brand dos/don'ts
- Name, category (
- System creates a
campaignsrow with status =preparedand settings JSONB.
Note: earlier drafts listed "payout model (fixed / fixed+rs), payout brackets, CPA/RS rates, contract length, termination clause, application deadline" — none of these are persisted columns. The campaign carries a
payout_type(manual/automatic) and per-campaignminimum_termsfloors; CPA/revshare rates live on the streamer (UC-9.3), not the campaign.
Postconditions: Campaign in Draft state. Not visible to streamers.
| Test | Assertion |
|---|---|
| — | not yet automated |
UC-2.2 · Edit Campaign
Actor: Operator Preconditions: Campaign exists with status prepared.
Flow:
- Operator selects campaign in the list; clicks "Edit".
- Form is pre-populated with existing data.
- Operator modifies any allowed fields and saves.
- System issues
PUT /api/campaigns/:id.
Constraints: Edit is presented in the UI only for Draft (prepared) campaigns, and this is now API-enforced. Once a campaign leaves prepared, PUT /api/campaigns/:id rejects any change to campaign terms (name, dates, budget, requirements, platforms, minimum_terms, …) with 409. Only status transitions, on-chain bookkeeping (escrow_id/network_id/funding_address) and brand assets stay editable. Funding writes happen while the campaign is still prepared, so they're unaffected; budget increases go through POST /api/campaigns/:id/top-up, not this endpoint.
| Test | Assertion |
|---|---|
| — | not yet automated |
UC-2.3 · Delete Campaign
Actor: Operator Preconditions: Campaign status is prepared.
Flow:
- Operator clicks "Remove" in the campaign detail panel.
- System issues
DELETE /api/campaigns/:id. - Campaign row is hard-deleted.
Constraints: Only prepared campaigns can be deleted (API enforced).
| Test | Assertion |
|---|---|
| — | not yet automated |
UC-2.4 · Fund Campaign (On-chain Escrow Deposit)
Actor: Operator Preconditions: Campaign is prepared. Operator has a connected wallet with sufficient USDC balance.
Flow:
- Operator opens campaign detail panel on
/operator/campaigns. FundCampaignPaneldisplays escrow deposit form (amount, network, timelock).- Operator approves USDC spend in MetaMask, then confirms the deposit transaction.
- System detects on-chain confirmation; updates campaign
escrow_id,funding_address, and status tofundedviaPUT /api/campaigns/:id. (There is no dedicated funding endpoint, andnetwork_idis not written at funding time — only at create.)
Postconditions: Campaign is visible to streamers. Funds locked in HTLC contract.
| Test | Assertion |
|---|---|
| — | not yet automated — on-chain / MetaMask, not API-testable |
→ Product Guide: Campaign Creation · Fund Deposit
UC-3 · Campaign Discovery & Application (Streamer)
UC-3.1 · Browse Campaigns
Actor: Public / Streamer Preconditions: None (public access).
Flow:
- User opens
/campaigns. - System fetches campaigns with status
fundedorin_progressfrom the API. - User can filter by tag (Beginner friendly, High budget, Closing soon) and sort (Featured, Highest budget, Newest).
- Campaign cards show operator name, category, platforms, budget, dates, min requirements.
Constraint — Streamer isolation (API-enforced): Authenticated streamers must never see other streamers' profiles, usernames, pricing, or deal terms. GET /api/streamers?public=1 returns 403 for any streamer-role session, and GET /api/streamers/:username returns 403 for a streamer viewing anyone other than themselves (a streamer may still read their own profile; admins are exempt).
| Test | Assertion |
|---|---|
| — | not yet automated — read-only list endpoint |
UC-3.2 · View Campaign Detail
Actor: Public / Streamer
Flow:
- User opens
/campaign/:id. - System fetches campaign details.
- Unauthenticated users see full public info with a sign-in CTA.
- Authenticated streamers see an application card (apply / already applied / approved).
- Tabbed sections: Overview, Requirements, Reward.
| Test | Assertion |
|---|---|
| — | not yet automated — read-only endpoint |
UC-3.3 · Submit an Offer (Streamer-initiated)
Actor: Streamer Preconditions: Streamer authenticated with status active (not suspended/archived). Campaign status funded or in_progress. No active (pending or accepted) offer already exists for this streamer on this campaign.
Flow:
- Streamer clicks "Apply" on the campaign detail page or card.
- Streamer adds an optional pitch message (no deal-term inputs on the streamer path).
- System creates an
offersrow viaPOST /api/campaigns/:id/applications(legacy shim) withinitiated_by = 'streamer',status = 'pending', and no expiry (streamer offers do not expire). - Operator receives a notification.
Postconditions: Offer visible in /profile/offers (streamer) and the campaign's Offers panel (operator).
Constraints:
- Streamer must have set a price bracket first, else
400(onboarding gate). - Suspended and archived streamer accounts are blocked (403).
- Duplicate active offer → 409 (one per streamer per campaign at a time).
⚠️ Known gap: the "five deal-term fields pre-filled from
minimum_terms" and "validate all starting terms ≥ campaign floor" behaviour applies only to the operator-initiated path (UC-4.4). The streamer "Apply" flow posts a message-only application — it sends nostarting_terms, so no floor validation runs streamer-side. (Field name isminimum_terms, notminimal_terms.)
| Test | Assertion |
|---|---|
| TC-OFFER-01 ⚠️ | POST /api/campaigns/:id/offers → 201; test uses initiated_by = "operator" — streamer-initiated path not yet separately asserted |
→ Product Guide: Applying to Campaigns
UC-3.4 · Withdraw Offer
Actor: Streamer Preconditions: Offer status is pending. Streamer is the initiating party.
Flow:
- Streamer opens
/profile/offers. - Clicks "Withdraw" on a pending offer.
- System issues
PATCH /api/offers/:idwithaction = 'withdraw'.
| Test | Assertion |
|---|---|
| TC-OFFER-04 | revoke_decline blocked by uq_offer_active when conflict exists → 409 |
| TC-OFFER-05 | revoke_decline succeeds when no conflict → offer returns to pending |
UC-4 · Offer Review (Operator)
UC-4.1 · View Campaign Offers
Actor: Operator Preconditions: Operator has funded campaigns.
Flow:
- Operator opens
/operator/activeand selects a campaign. - System fetches
offersfor the campaign viaGET /api/campaigns/:id/offers. - Offers listed with streamer username, Trust Score, starting terms, and current status.
| Test | Assertion |
|---|---|
| — | not yet automated — read-only list endpoint |
UC-4.2 · Accept Offer → Open Negotiation
Actor: Operator Preconditions: Offer status pending, initiated by streamer (or operator is the streamer receiving the offer back). Campaign status funded or in_progress.
Flow:
- Operator selects a pending offer and clicks "Accept".
- System issues
PATCH /api/offers/:idwithaction = 'accept'. - API transitions offer status to
accepted. - API auto-creates a
negotiationsrow seeded from the offer'sstarting_terms, withoffer_idset and initialturn='operator'(since streamer proposed). - Both parties are notified.
Postconditions: Negotiation exists with status in_progress.
| Test | Assertion |
|---|---|
| TC-OFFER-02 | Duplicate accept is idempotent or returns 409 — offer never left in inconsistent state |
UC-4.3 · Decline Offer
Actor: Operator
Flow:
- Operator clicks "Decline" on a pending offer.
- System issues
PATCH /api/offers/:idwithaction = 'decline'. - Offer transitions to
declined. Streamer can re-apply.
| Test | Assertion |
|---|---|
| TC-OFFER-03 | action = decline → offer status declined |
UC-4.4 · Send Offer to Streamer (Operator-initiated)
Actor: Operator Preconditions: Operator has at least one funded/in-progress campaign (Tier 2 directory access). Streamer has no active offer on this campaign.
Flow:
- Operator opens a streamer's public profile at
/s/:username. - Clicks "Send Offer" and selects a campaign.
- Offer form shows five deal term fields pre-filled from
campaign.minimum_terms. - Operator adjusts values, adds an optional message, and sets an expiry date (default: 7 days).
- System validates starting terms ≥ campaign floors.
- System creates
offersrow withinitiated_by = 'operator',status = 'pending',expires_atset. - Streamer receives a notification.
Postconditions: Offer visible to both parties. Streamer can accept, decline, or let it expire.
Rate limit: 20 operator-initiated offers per 24-hour window.
| Test | Assertion |
|---|---|
| TC-OFFER-01 ⚠️ | POST /api/campaigns/:id/offers with initiated_by = "operator" → 201 |
| — | Expiry enforcement and rate-limit (20/24h) not yet automated |
→ Product Guide: Offer Review & Deal Negotiation · Budget Allocation
UC-4B · Deal Negotiation
UC-4B.1 · Propose or Approve a Term
Actor: Operator or Streamer Preconditions: Negotiation status = in_progress. It is this actor's turn.
Flow:
- Actor opens the negotiation panel and sees all five terms with current proposed values and approval states.
- Approve — Actor clicks "Approve" on a term they agree with.
- System sets
op_okorstr_ok=trueon that term.
- System sets
- Propose — Actor enters a new value for a term.
- System updates
valueandproposed_by. - Resets the other party's
okflag tofalseon that term.
- System updates
- After either action, the system checks whether all five terms have both
op_ok = trueandstr_ok = true. - If all agreed → negotiation status transitions to
agreed;agreed_atis recorded. - Otherwise →
turnadvances to the other party.
Constraints: Values should remain ≥ campaign floor values throughout negotiation. The server only enforces value >= 0 on propose; the campaign-floor check is client-side only (known gap).
| Test | Assertion |
|---|---|
| TC-NEG-01 | Streamer approves all 5 terms → status = agreed, agreed_at set, turn = NULL |
UC-4B.2 · Cancel Negotiation
Actor: Operator or Streamer Preconditions: Negotiation status = in_progress.
Flow:
- Actor clicks "Cancel Negotiation".
- System issues
POST /api/negotiations/:id/cancel. - Negotiation transitions to
cancelled. A new offer can be created to restart.
| Test | Assertion |
|---|---|
| — | not yet automated |
UC-4B.3 · Fund Deal
Actor: Operator Preconditions: Negotiation status = agreed.
Flow:
- Operator opens the agreed negotiation and clicks "Fund Deal".
- System confirms the negotiation is
agreedand the operator is the correct party. - Operator supplies on-chain parameters (escrow ID, network, funding address, allocation ID and amount).
- Operator signs the HTLC allocation transaction in MetaMask.
- System creates a
dealsrow with:negotiation_idFKagreed_terms— immutable JSONB snapshot of the five negotiated values- On-chain fields (
escrow_id,network_id,funding_address,allocation_id,allocation_amount) status = 'active'
- Both parties notified. Streamer's delivery panel becomes active.
Postconditions: Deal exists and is active. Agreed terms are permanently frozen.
| Test | Assertion |
|---|---|
| TC-NEG-02 | allocation_amount exceeds budget cap → 409 |
| TC-NEG-03 | allocation_amount within budget cap → 201, status = active |
| TC-NEG-04 | wallet_address snapshotted from streamer_addresses at funding time |
→ Product Guide: Offer Review & Deal Negotiation
UC-5 · Stream Delivery (Streamer)
UC-5.1 · Submit Stream Session Proof
Actor: Streamer Preconditions: Application status approved. Streamer has a slot_index to submit for.
Flow:
- Streamer opens
/profile/campaigns. - Selects approved campaign and an available slot.
- Pastes stream/VOD URL.
- Optionally selects a tracked Kick session from the dropdown.
- Clicks "Submit".
- System creates
session_submissionsrow (status =pending_review).
Constraints: Multiple attempts per slot are allowed (the attempt counter increments); the API blocks resubmission only once a slot is confirmed (409). There is no DB unique constraint on session_submissions(offer_id, slot_index) — uniqueness is enforced one level down, on payout_records (uq_payout_records_offer_slot), so a slot pays out at most once. URL is currently required (tracked-session-only path is a known gap — F-4).
| Test | Assertion |
|---|---|
| TC-DEL-01 | slot_index outside allocation range → 400, error lists valid indices |
| TC-DEL-02 | Valid slot_index → 201, status = pending_review, attempt = 1 |
| TC-DEL-03 | Slot already confirmed → resubmission blocked with 409 |
→ Product Guide: Delivery & Submission
UC-6 · Delivery Review & Payout (Operator)
UC-6.1 · Review Submission
Actor: Operator Preconditions: session_submissions row with status pending_review.
Flow:
- Operator opens
/operator/active. - Submission listed under the relevant application.
- Operator reviews stream URL and any notes.
- Operator clicks "Confirm" or "Reject".
| Test | Assertion |
|---|---|
| — | not yet automated — read-only list view |
UC-6.2 · Confirm Submission → Release Payout
Actor: Operator Preconditions: Submission pending_review. Streamer KYC fully approved — both kyc_age_status and kyc_country_status = approved (API enforced — F-1). The legacy single kyc_status column was dropped in migration 0119; KYC is per-track since 0113.
Flow:
- Operator confirms submission.
- API:
- Checks both
kyc_age_statusandkyc_country_status=approvedon the streamer. Returns 403 if either track isn't approved. - Reads per-slot amount from
allocation_preimages.slots[slot_index]. - Inserts
payout_recordsrow with amount, wallet snapshot,allocation_id. - Updates
session_submissions.status = 'confirmed'.
- Checks both
- Operator retrieves preimage for the slot (stored in
allocation_preimages). - Operator sends preimage to streamer off-platform (or automated via payout_type =
automatic). - Streamer calls
withdraw()on-chain using the preimage. - Streamer (or frontend) submits tx hash via
PATCH /api/session-submissions/:id/tx.
Postconditions: payout_records row has tx_hash. Streamer has received USDC.
| Test | Assertion |
|---|---|
| TC-DEL-04 | Confirming last slot → deals.status = completed (auto-complete regression) |
| TC-DEL-05 | Confirming partial slot → deals.status remains active |
| TC-DEL-06 | Streamer KYC not approved → confirmation blocked with 403 |
UC-6.3 · Reject Submission
Actor: Operator
Flow:
- Operator clicks "Reject" on a submission.
- System updates
session_submissions.status = 'rejected'. - Streamer can resubmit (no enforcement on retry count — F open).
| Test | Assertion |
|---|---|
| — | not yet automated |
→ Product Guide: Delivery Review · Payment Withdrawal · Refund
UC-7 · Earnings Tracking (Streamer)
UC-7.1 · View Earnings Dashboard
Actor: Streamer Preconditions: Authenticated.
Flow:
- Streamer opens
/profile/dashboard. - System fetches
payout_recordsfor the streamer (GET /api/streamers/:username/payouts). - Dashboard shows:
- Total Earned (sum of all
payout_records.amount_usdc) - Earned This Month (filtered by current month)
- In Escrow (sum of approved allocations not yet confirmed)
- Pending (count of pending submissions)
- Recent activity feed
- Total Earned (sum of all
| Test | Assertion |
|---|---|
| — | not yet automated — read-only aggregation |
UC-7.2 · View Application / Payout History
Actor: Streamer
Flow:
- Streamer opens
/profile/applications. - All applications listed with campaign name, status, allocation amount, confirmed amount.
- Streamer can expand each application to see submission-level detail and payout records.
| Test | Assertion |
|---|---|
| — | not yet automated — read-only list |
→ Product Guide: Profile
UC-8 · Streamer Profile & Verification
UC-8.1 · Streamer Registration
Actor: Public Preconditions: None, or operator has a funded account to view the streamer directory.
Flow:
- User opens
/streamer-signup. - Accepts consent, then connects their Kick account via OAuth — this is now the only signup path.
- System derives a unique username from the Kick handle and links a
streamer_channelsrow (Kick fields populated). - System creates
streamersrow (status =pending).
Postconditions: Account pending admin activation.
Note: the legacy email-+-4-digit-PIN signup was retired (the API branch still exists but has no live frontend caller). Email/PIN is bypassed when a
kick_signup_tokenis present. Country/languages are completed later in the profile, not at signup.
| Test | Assertion |
|---|---|
| TC-AUTH-02 | PIN sent and verified for streamer email → session established |
UC-8.2 · KYC Document Upload
Actor: Streamer Preconditions: Streamer authenticated.
Flow:
- Streamer opens
/profile/kyc. - Sumsub WebSDK is the primary KYC path (gated by
streamer-kyc-sumsub-enabled); it drives the per-track statuses. Manual document upload (identity and/or proof of address, JPEG/PNG, max 5MB → S3 +streamer_documentsrow) is the fallback and the Sumsub-FINAL-rejection recovery path. - Verification resolves two independent tracks:
kyc_age_status(identity / liveness) andkyc_country_status(proof of address). Each is set toapproved/rejectedby Sumsub webhooks or by an admin in the admin app. (The legacy singlekyc_statuscolumn was dropped in 0119.) - If a track is rejected, the streamer can re-verify / re-upload after viewing the rejection reason.
| Test | Assertion |
|---|---|
| — | not yet automated — requires S3 or mock bucket |
UC-8.3 · Connect / Manage Wallets
Actor: Streamer
Flow:
- Streamer opens
/profile/wallet. - Connects MetaMask wallet; system saves to
streamer_addresses. - Multiple addresses supported; each can have an optional label.
- Wallet address is used as payout destination in
payout_records.
| Test | Assertion |
|---|---|
| TC-NEG-04 ⚠️ | streamer_addresses row read at deal funding — wallet snapshotted correctly; CRUD not tested |
UC-8.4 · Update Profile & Deal Preferences
Actor: Streamer
Flow:
- Streamer opens
/profile/personal. - Edits personal info (country, languages) and streaming preferences (categories, GEOs, deal types, rates).
- Updates operator-facing bio.
- System issues
PUT /api/streamers/:username.
| Test | Assertion |
|---|---|
| — | not yet automated |
UC-8.5 · View Trust Score
Actor: Streamer / Public
Flow:
- Streamer opens
/trust-scoreor operator views/s/:username. - System displays trust score breakdown across 5 components: channel age, followers, average viewers, viewer/follower ratio, and stream frequency. (There is no literal "engagement" pillar —
viewer_follower_ratiois the closest proxy.) - Improvement suggestions displayed per pillar.
- Score is recomputed from
streamer_trust_scoresvia the poller/webhook pipeline.
| Test | Assertion |
|---|---|
| — | not yet automated — score computed externally by poller |
→ Product Guide: Registration & Onboarding · Profile · Wallet · Trust Score
UC-9 · Streamer Discovery (Operator)
UC-9.1 · Browse Streamer Directory
Actor: Operator Preconditions: Operator authenticated. Access is gated — redirects to /entry if not an operator.
Access to the streamer directory is tiered by whether the operator has at least one campaign with status funded or in_progress.
Enforcement (server-side): the funded-campaign tier split is enforced by the API.
GET /api/streamers?public=1reads the caller's session; when the caller is an operator with nofunded/in_progresscampaign (Tier 1), every identity-revealing field (username, bio, Discord, Telegram, country, Kick slug/handle/display name/avatar, Twitter handle) is stripped from each row on top of the standard PII-strip (UC-9.3) — leaving the anonymised preview.GET /api/streamers/:username(the/s/:usernameprofile page source) returns 403 for a Tier-1 operator. The gate is thecampaigns.status IN ('funded','in_progress')EXISTS check (operatorHasFundedCampaigninapi/src/streamers.ts), and it fails closed (anonymise on any error). Non-operator callers (logged-out visitors, streamers, admins) are unaffected by the tier gate.
Tier 1 — No funded campaign (anonymous preview)
- Operator opens
/streamers. - System detects no
fundedorin_progresscampaign for this operator. - API returns an anonymised streamer list: follower count, engagement score, trust score, verified badge, platform icons, categories, and approximate price bracket.
- Username, bio, contact handles, country, and all identifying fields are withheld by the API.
- Free-text search is disabled.
- Each card shows a "Fund a campaign to unlock full profiles" CTA.
- Clicking a streamer card or navigating to
/s/:usernamereturns403.
Tier 2 — At least one funded campaign (full directory)
- Operator opens
/streamers. - System detects at least one
fundedorin_progresscampaign. - API returns full profiles: username, bio, country, languages, streaming categories, deal preferences, trust score, follower count, verification badge, platforms.
- Free-text search across username and categories is enabled.
- Operator can filter by platform (Kick / Twitch) and streaming category.
- Streamer cards show all public profile fields. Exact pricing is never shown — see UC-9.3.
- Operator can click through to the full public profile at
/s/:username(see UC-9.2).
Note: The invite flow (operator sends a direct deal offer) is a separate proposal — see the offer flow (UC-4.4).
| Test | Assertion |
|---|---|
| — | not yet automated — tier-gating logic and list response |
UC-9.2 · View Streamer Public Profile
Actor: Operator (Tier 2 only) Preconditions: Operator authenticated and has at least one funded or in_progress campaign.
Flow:
- Operator clicks a streamer card on
/streamersor navigates directly to/s/:username. - System checks the operator's tier. If Tier 1, returns
403with body:{ "error": "Fund a campaign to view full streamer profiles." } - For Tier 2 operators, system returns the full public profile:
- Avatar, username, verified badge, trust score
- Bio (operator-facing), country, languages
- Streaming categories, platform handles
- Deal type preferences, price bracket (not exact rate — see UC-9.3)
- Follower count, engagement score, live status
- Operator can initiate the invite flow from this page (future — see proposal).
| Test | Assertion |
|---|---|
| — | not yet automated — read-only + tier guard |
UC-9.3 · Pricing Display Rules
Actor: Operator, System Preconditions: Streamer has set deal_cpa_rate and/or deal_revshare_rate.
Rule: Exact streamer rates (deal_cpa_rate / deal_revshare_rate) are never surfaced in the directory or on public profiles — both are stripped from ?public=1 and /s/:username responses.
Flow:
- The streamer picks a
price_bracketthemselves — it is an explicit column (migration 0153), not derived from the raw rate fields. The displayed bracket is the streamer's stated band. - The bracket is shown on streamer cards (Tier 2 + Tier-1 preview) and on the public profile.
- Exact figures are revealed only after the deal terms are negotiated and an HTLC allocation (
allocation_preimagesrow) exists — the operator's allocation panel then shows the agreed amount.
Bracket values (codes from migration 0153_streamer_price_bracket_v2.sql; labels from frontend/src/utils/priceBracket.ts):
| Code | Display label |
|---|---|
0_50 | Under $50 |
50_200 | $50 – $200 |
200_500 | $200 – $500 |
500_1000 | $500 – $1,000 |
over_1000 | $1,000+ |
Note: migration 0152 introduced the column with a different set of bands; 0153 superseded it with the codes above. Earlier drafts of this doc cited 0152's
<$100 … $10k+ranges — those are stale.
| Test | Assertion |
|---|---|
| — | not yet automated — bracket mapping logic |
→ Product Guide: Dashboard & Streamer Discovery
UC-10 · Fund Breakdown Monitoring (Operator)
UC-10.1 · View Campaign Fund Breakdown
Actor: Operator
Flow:
- Operator opens
/operator/campaignsor/operator/active. - Each campaign card shows a
FundDonutchart with four segments:- Rewarded — confirmed payout_records sum
- Escrowed — approved allocation_amount minus rewarded
- Refunded — refunded allocation_amount
- Unallocated — remainder of budget_cap_usdc
- Detail panel shows the donut with legend and exact amounts.
| Test | Assertion |
|---|---|
| — | not yet automated — read-only aggregation |
→ Product Guide: Fund Deposit
UC-11 · Admin Operations
UC-11.1 · Impersonate Streamer / Operator
Actor: Admin
Flow:
- Admin creates an impersonation token via admin app (stored in
impersonation_tokenstable). - Admin opens
/auth/impersonate?token=UUID. - System validates token (single-use, time-limited).
- Session is established for the target account.
- Impersonation banner is displayed across all pages for the duration of the session.
| Test | Assertion |
|---|---|
| — | not yet automated — admin-only, single-use token |
UC-11.2 · Manage Invitations
Actor: Admin (via admin app at admin.verifluence.io)
Flow:
- Admin creates
invitationsrow with target email. - System emails the invitation link to the operator.
- Admin can view invitation status (pending / used / expired).
| Test | Assertion |
|---|---|
| — | not yet automated — admin-only |
→ Admin portal at
admin.verifluence.io(no product guide page — internal tool only)
UC-12 · Deal Negotiation
Status: Implemented — see UC-4B for the full negotiation and deal funding flow.
The Offer / Negotiation / Deal entity model replaces the earlier prototype described here. Minimum term floors are set per campaign (not per streamer); both operator- and streamer-initiated offers are supported via
initiated_by.Anchor retained: the roadmap (Milestones.vue / roadmap.md / milestones.md) deep-links UC-12.x milestone items to
#uc-12-deal-negotiation.
Out of Scope (Not Yet Implemented)
| Feature | Status |
|---|---|
| Campaign Paused / Archived status | Not built — status enum is prepared/funded/in_progress/completed/cancelled |
| Campaign Analytics (VOD metrics, conversions) | Not built |
| Referral program | Not built |
| Dispute / mediation flow | Not built |
| Tracked-session-only delivery (no URL) | Not built (F-4) |
| Server-side prepared-only campaign edit guard | Not built (UC-2.2 gap) |
| Streamer-side deal-term floor validation | Not built (UC-3.3 gap — only operator path validates) |
| Persisted operator consent at sign-up | Not built (UC-1.1 gap — no consent column on operators) |
Recently resolved (previously listed here)
These were tracked as gaps in earlier revisions and are now implemented:
| Former gap | Now |
|---|---|
| Delivery child tables remapped (migration 0079) | Applied — remapped to offer_id (not deal_id); all three tables carry offer_id NOT NULL |
| Application auto-complete on last slot (F-3) | Implemented — confirming the last slot sets deals.status = 'completed' |
| Slot index validation against allocation (F-5) | Implemented — out-of-range slot_index → 400 listing valid indices |
| Budget cap enforcement on allocation (F-8) | Implemented — deals.ts sums existing allocations, rejects over-cap with 409 |
| In-app notifications (real-time) | Implemented — Pusher channels (API pusher.ts + FE usePusher) |
| Negotiation UI (frontend components) | Implemented — operator + streamer negotiation pages and components/negotiations/ |