Skip to content

Design: Verified Social-Event Settlement Floor

Generated by /office-hours on 2026-08-29 Branch: codex/verified-social-settlement-floor Repo: lovetimo0421/yumina Status: APPROVED Mode: Startup

Problem Statement

The social-share event can complete with finalDueMushies = 0. The settlement is technically correct under the cumulative reward tiers, but the user sees no new wallet transaction and reasonably concludes that final settlement never happened.

The product decision is to guarantee a visible final payment of at least 1,000 Mushies per user, but only when final review can verify that the user still has at least one qualifying public post. A user whose reviewed posts are all unavailable, deleted, or private receives no settlement-floor reward.

Production Evidence

  • Current event ID: 8ca7dcae-150e-44e7-8a8f-7c9ea7b5bd60.
  • 41 users have completed settlements in the current event.
  • 31 users have at least one latest admin-reviewed snapshot marked official_link_check + accessible.
  • 28 of those verifiable users currently have a zero final payment and would receive 1,000 Mushies.
  • 10 users have no accessible post at final review and remain ineligible.
  • Current-event incremental cost: 28,000 Mushies.
  • The reported user @loww qualifies because two reviewed posts were marked accessible.

Decisions

  • The floor is per event user, never per platform.
  • Eligibility requires at least one entry included in the settlement whose deterministic latest admin-reviewed metric snapshot has resolution = official_link_check and linkStatus = accessible.
  • Initial evidence alone does not qualify a user when every post is unavailable at final review.
  • Performance entitlement remains unchanged. The floor is a separate settlement-completion reward, not an extra score tier.
  • The exact contract is floorTopUp = qualifyingPost ? max(1,000 - calculatedFinalDue, 0) : 0; score-based entitlement and calculatedFinalDue remain unchanged for every user.
  • The current completed event is backfilled using an idempotent adjustment grant. Future settlements apply the same rule during finalization.
  • A successful floor payment emits a specific notification only after the credit transaction is applied.

Approaches Considered

  • Chosen: verified per-user final floor. Preserves the score ladder, gives every verifiable participant a visible final payment, and caps the current incremental cost at 28,000 Mushies.
  • Rejected: current-event-only compensation. Fixes this incident but guarantees the same confusing zero-payment settlement in future events.
  • Rejected: per-platform extra 1,000. Would change the base platform reward from 1,000 to 2,000 and disproportionately reward platform count.

Keep performance math auditable instead of overwriting finalDueMushies:

  • Keep the existing performance final grant unchanged. Create the floor top-up as a separate phase = adjustment Mushies grant so the grant row is the durable source of truth for its amount and status.
  • Add a nullable, typed reward-grant purpose field with verified_settlement_floor as the new value. Existing grants remain null; code never identifies floor grants by parsing their idempotency key. Add an index on (purpose, status, createdAt) for reconciliation.
  • Expose calculatedFloorTopUp, calculatedFinalPayoutMushies, appliedPerformanceTopUpMushies, appliedFloorTopUp, appliedFinalPayoutMushies, and floorGrantStatus by reading the typed grants in API responses and admin preview. Calculated values show entitlement; applied values sum only grants with status = applied. Hide the completion row when both calculated and applied floor amounts are zero.
  • Store the winning metric snapshot IDs, verifiedAt values, resolution/link status, accessible-post count, floor amount, and rules version in settlement/admin audit metadata.
  • Use the versioned global constant SOCIAL_EVENT_VERIFIED_FINAL_FLOOR_MUSHIES = 1_000. New social events are created with rulesVersion = 2; existing events retain their persisted rules version and are never automatically bumped. Ordinary settlement applies the floor only when the persisted version is 2 or later; the named current event is handled only by its explicit backfill.
  • Add an optional notification dedupeKey with a database uniqueness constraint on (userId, type, dedupeKey). The floor notification uses verified-settlement-floor:<grantId> and ON CONFLICT DO NOTHING, so wallet retries and notification retries are independently safe.

The settlement preview excludes grants with purpose = verified_settlement_floor from performance committedMushies, while leaving genuine performance adjustments in that calculation. This preserves the existing identity totalEntitlement - committedMushies = finalDueMushies.

“Latest” always means verifiedAt DESC, createdAt DESC, id DESC among snapshots with a non-null admin reviewer. A user-supplied user_evidence snapshot never qualifies by itself.

Grant and Backfill Design

Future settlement

  1. Build the latest-reviewed-snapshot map with the deterministic ordering above while the settlement entry rows are locked.
  2. Apply one shared predicate: reviewerAdminId != null, resolution === official_link_check, and linkStatus === accessible.
  3. Calculate the existing score-based finalDueMushies unchanged.
  4. For a rules-v2 event, calculate floorTopUp = hasAccessiblePost ? max(1,000 - finalDueMushies, 0) : 0.
  5. Create the existing performance grant plus a typed purpose = verified_settlement_floor adjustment grant when floorTopUp > 0. Its exact key is event:<eventId>:user:<userId>:verified-settlement-floor:rules-v<rulesVersion>:mushies. After an idempotency conflict, fetch the existing grant and hard-fail if its purpose, amount, event, user, settlement, or kind differs.
  6. Persist the exact winning snapshot IDs in the settlement audit and emit the floor notification after that adjustment grant is applied. For future finalizations, the special message replaces the generic social_event_settled message when the floor top-up is positive; a failed grant uses the existing failure message.

Current completed event

  1. Generate a read-only manifest for every completed settlement in the named event, including excluded users. Each row contains settlement ID, user ID, deterministic winning snapshot IDs and timestamps, calculated final due, floor top-up, eligibility decision or exclusion reason, and a manifest fingerprint. The approved manifest freezes eligibility at its recorded cutoff.
  2. Select completed settlements with floorTopUp = max(1,000 - finalDueMushies, 0) > 0 and at least one snapshot satisfying the shared eligibility predicate. Production currently has no settlement with 0 < finalDueMushies < 1,000, but the backfill still uses the general formula.
  3. In one database transaction, take an event/version advisory lock, lock every completed settlement row in deterministic ID order, re-read every winning snapshot, validate the full eligible-and-excluded manifest, and only then create pending grants. Any changed row or fingerprint rolls back all grant creation before wallet processing begins.
  4. Create one phase = adjustment, purpose = verified_settlement_floor Mushies grant per eligible user with idempotency key event:<eventId>:user:<userId>:verified-settlement-floor:backfill-v1:mushies. An existing key is accepted only when all immutable grant fields match.
  5. Process grants through the existing atomic wallet and ledger path. The adjustment grant itself is the settlement-completion amount/status record, so a crash cannot leave separate settlement metadata stale.
  6. After a grant is applied, call ensureVerifiedSettlementFloorNotification(grantId), which inserts the special notification with its deterministic dedupe key. The current-event message supplements the earlier generic completed-settlement notification. The same ensure helper runs after grant retries, even when the wallet result is already applied.
  7. Add a reconciliation command that finds applied typed floor grants anti-joined to notifications by dedupe key and calls the same ensure helper. The notification unique index makes recovery safe after either crash boundary.
  8. Produce an operator summary with eligible, applied, already-applied, failed, notified, and excluded counts before and after the production run.

Notification Copy

Notification message proposed for the current event:

你的活动终审已经完成。我们已核实你提交的公开帖子。虽然本次没有产生额外的传播分档位补发,你仍获得 1,000 Mushies 终审完成奖励,现已到账。感谢你的分享与耐心等待!

The notification payload should include eventId, settlementId, grantId, amount, verifiedPostCount, rewardStatus, dedupeKey, and a dedicated presentation key. The amount is formatted from the actually applied floor top-up. Users who have no qualifying final-reviewed post do not receive this message. The new copy is translated in every supported locale.

User-Facing Semantics

  • “平台累计奖励” remains the score-tier entitlement.
  • “终审补发” remains the score-tier difference.
  • A separate “终审完成奖励” displays the actual floor top-up when applied and is hidden when the amount is zero.
  • “本次终审应得” displays the calculated performance top-up plus calculated floor top-up.
  • “本次终审到账” displays only applied performance grants plus the applied settlement-completion grant.

This naming avoids telling a user that their score was higher than it was.

Verification

  • Eligible, zero score-based due: one 1,000 Mushies grant and one success notification.
  • Eligible, 3,000 score-based due: 3,000 total final payment and no extra floor amount.
  • Mixed accessible and unavailable platforms: qualifies once per user.
  • All final-reviewed posts unavailable: no floor grant and no floor notification.
  • User evidence without an accessible admin official-link review: does not qualify.
  • Missing or unreviewed latest snapshots: does not qualify and blocks ordinary finalization under the existing unresolved-entry guard.
  • An older accessible snapshot followed by a latest unavailable snapshot: does not qualify from the old snapshot.
  • Tied verifiedAt timestamps: createdAt and then snapshot ID select one deterministic winner.
  • A snapshot or settlement changed after dry run: the manifest check aborts before any grant is created.
  • Repeated finalize, backfill, or retry: no duplicate credit transaction or notification.
  • An existing idempotency key with a different amount or target: hard failure, never silent ON CONFLICT DO NOTHING acceptance.
  • Crash after grant applied but before notification: notification retry inserts it once without touching the wallet.
  • Crash after notification insert: the unique dedupe key makes the retry a no-op.
  • Reconciliation finds an applied typed floor grant with no matching notification and repairs it exactly once.
  • Pending or failed floor grants expose appliedFloorTopUp = 0; only an applied grant contributes to “本次终审到账”.
  • A failed performance grant likewise contributes 0 to appliedPerformanceTopUpMushies; calculated entitlement remains visible separately.
  • Floor grants are excluded from performance commitment math while genuine performance adjustments remain included.
  • Grant failure: no success notification; existing retry/failure status remains visible.
  • Admin preview shows eligibility, score due, floor amount, and actual final payment before confirmation.
  • Production dry run for the current event reports 28 eligible zero-due users and 28,000 Mushies before applying changes.

Rollout

  1. Apply an expand-first, backward-compatible production migration before the code deploy: add nullable reward-grant purpose and notification dedupeKey columns, add the grant-purpose reconciliation index, and create the partial unique notification index on (userId, type, dedupeKey) WHERE dedupeKey IS NOT NULL using the production-safe concurrent index workflow. Verify the columns and indexes while the old code is still running.
  2. Deploy the rules-v2 settlement calculation, typed floor-grant derivation, notification dedupe/reconciliation support, presentation, and tests. Verify health without running the historical backfill.
  3. Run a read-only dry run and compare it to the known 28-user / 28,000-Mushies baseline.
  4. Require explicit operator confirmation for the production backfill.
  5. Apply grants, verify ledger/grant/reconciliation counts, then sample the resulting notifications.

Rollout cohorts are explicit: the backfill targets only completed settlements for the named current event. Any current-event settlement still processing or failed at apply time is excluded, resolved through the existing retry path, and requires a newly generated manifest after it becomes completed. Legacy events with missing rules-v2 enablement never inherit the floor. New rules-v2 events use it during ordinary finalization.

Success Criteria

  • Every completed, verifiable participant in the named current event and every enabled rules-v2 event receives at least 1,000 Mushies during final settlement.
  • No user with all posts unavailable receives the settlement-floor reward.
  • No user can receive the floor more than once per event.
  • Wallet credit, reward grant, settlement metadata, and notification remain reconcilable.
  • The reported user receives exactly 1,000 Mushies from the current-event backfill.

The Assignment

Implement the approved verified settlement floor, verify it locally, and prepare a production dry run before any backfill mutation.

What I noticed about how you think

  • You changed the goal from “make the ledger mathematically correct” to “make final settlement visibly real to the user.” That is the right product framing for this incident.
  • You immediately added “得是能核实有帖子的情况,” which prevents the goodwill reward from becoming a payout for deleted or unverifiable participation.