Skip to content

fix(commissions): enforce DB idempotency, unify commission engine, and harden webhook money paths - #14

Merged
TechHypeXP merged 1 commit into
mainfrom
fix/wave1a-commission-integrity
Jul 16, 2026
Merged

fix(commissions): enforce DB idempotency, unify commission engine, and harden webhook money paths#14
TechHypeXP merged 1 commit into
mainfrom
fix/wave1a-commission-integrity

Conversation

@TechHypeXP

@TechHypeXP TechHypeXP commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Implements Wave 1A fixes: migrations, CommissionRepositoryAdapter atomic updates, unifying referrals/uppromote calculation engines, adding payout idempotency checks, and Shopify webhook write checks.

Summary by Sourcery

Strengthen commission and payout integrity by enforcing database idempotency, centralizing commission calculation through the repository adapter, and hardening webhook-driven money flows.

New Features:

  • Introduce an atomic referral stats update RPC and wire commission processing through a unified CommissionRepositoryAdapter.
  • Add payout idempotency checks for UpPromote webhooks to prevent duplicate commission_payout entries.
  • Provide a QA-Intel driven quality verification script and Vitest config with path aliases and env loading for tests.

Bug Fixes:

  • Ensure commissions are unique per referrer and order via a deduplication migration and unique index to prevent double-commissions.
  • Fix race conditions and TOCTOU issues in commission creation by switching to upsert-based writes and robust duplicate handling.
  • Guard JSON parsing and Supabase operations in webhook and API routes, failing safely on bad payloads or write errors.
  • Prevent long-lived idempotency locks by using short Redis TTLs during processing and logging failures when marking webhooks as processed.
  • Add idempotency for UpPromote payouts to avoid duplicate payout records when webhooks are retried.

Enhancements:

  • Refine commission tier and record typing to support custom tiers and tier multipliers consistently across referral logic.
  • Normalize path handling and error observability in the quality engine verification script and improve CLI modes and reporting.
  • Tighten logging and error handling in the idempotency store adapter for better visibility into Redis status and parsing issues.
  • Improve Shopify webhook product and inventory handlers with explicit Supabase error checks to avoid silently corrupting catalog data.
  • Update UpPromote referral commission handling to delegate calculations and stats updates to the commission repository adapter.

Build:

  • Add a Vitest configuration file with manual .env.local loading and module aliasing to integrate tests with the existing codebase.

CI:

  • Introduce a verify-quality-engine script to run the QA-Intel quality engine with diff/full scan modes, baseline comparison, and CI gating on critical and high findings.

Deployment:

  • Adjust Redis idempotency key TTL semantics to separate short-lived processing reservations from longer-lived completion markers.

Documentation:

  • Add a Hex-Diva Wave 1 code review matrix documenting layout-related findings, severities, and recommended fixes.
  • Document the 2026-07-16 execution plan for stabilization and commerce core waves, including ownership and PR gates.

Chores:

  • Update the agent ledger with Wave 1 stabilization session metadata and task ownership.
  • Add migration 009 for commission integrity and atomic referral stats updates, aligning runtime with schema.

Summary by cubic

Enforces single-commission per (referrer, order), routes all commission math through one engine, and hardens Shopify/UpPromote webhook money paths with strict idempotency and error handling.

  • Bug Fixes

    • Commission DB integrity: adapter now upserts on referrer_id,order_id to avoid TOCTOU; duplicate fetch fallback added.
    • Unifies commission calculation via src/lib/referrals (tier + rate) and uses the adapter in the uppromote webhook.
    • UpPromote payouts: adds an idempotency guard to skip duplicate payout records.
    • Shopify webhook: checks every Supabase write/read error; failed DB writes prevent marking webhooks as completed.
    • Idempotency store: short 60s TTL for “processing” locks, keep 7-day TTL for “processed”; compares and logs set results; better JSON/error logging.
    • Process-order API: parses JSON once and returns 400 on invalid bodies.
    • Minor: safe non-mutating sort in uppromote.ts; added commission fields to Database types.
  • Migration

    • 009_commission_integrity.sql: dedupes existing commissions, adds unique index on (referrer_id, order_id), and introduces update_referral_stats_atomic RPC for race-safe stats updates.

Written for commit 8a6c1c1. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • Bug Fixes

    • Prevented duplicate commission records and repeated payout processing.
    • Improved reliability when processing referrals, orders, inventory, and product updates.
    • Added clearer handling for invalid request data and failed webhook operations.
    • Reduced risks from concurrent commission processing and inconsistent referral totals.
  • Improvements

    • Added support for custom commission tiers and expanded commission tracking details.
    • Improved webhook retry and idempotency behavior.
  • Documentation

    • Added testing guidance and release execution documentation.

@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@netlify

netlify Bot commented Jul 16, 2026

Copy link
Copy Markdown

Deploy Preview for hex-diva ready!

Name Link
🔨 Latest commit 8a6c1c1
🔍 Latest deploy log https://app.netlify.com/projects/hex-diva/deploys/6a588ed52929740008f02455
😎 Deploy Preview https://deploy-preview-14--hex-diva.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@vercel

vercel Bot commented Jul 16, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
hex-diva Ready Ready Preview, Comment Jul 16, 2026 7:57am

@sourcery-ai

sourcery-ai Bot commented Jul 16, 2026

Copy link
Copy Markdown

Reviewer's Guide

This PR hardens commission and payout processing by enforcing database-level idempotency, centralizing commission calculation through a repository adapter, and adding explicit error-handling and write guards on money-related webhooks and engines, plus introduces a quality engine verifier script and test config plumbing.

Sequence diagram for UpPromote order attribution commission processing

sequenceDiagram
  actor UpPromote
  participant UppromoteWebhookRoute as UppromoteWebhookRoute
  participant CommissionRepositoryAdapter as CommissionRepositoryAdapter
  participant Supabase as Supabase

  UpPromote->>UppromoteWebhookRoute: handleOrderAttributed(data)
  UppromoteWebhookRoute->>Supabase: select referrals by referral_code
  Supabase-->>UppromoteWebhookRoute: referral
  UppromoteWebhookRoute->>CommissionRepositoryAdapter: processOrderCommission(referrer_id, order_id, amount)
  CommissionRepositoryAdapter->>Supabase: select referral_stats.total_conversions
  Supabase-->>CommissionRepositoryAdapter: stats
  CommissionRepositoryAdapter->>Supabase: upsert commissions onConflict referrer_id,order_id
  Supabase-->>CommissionRepositoryAdapter: commission row
  CommissionRepositoryAdapter-->>UppromoteWebhookRoute: commission
  UppromoteWebhookRoute->>Supabase: update referrals (conversions, commission_amount)
  UppromoteWebhookRoute->>CommissionRepositoryAdapter: updateReferralStats(referrer_id)
  CommissionRepositoryAdapter->>Supabase: select referral_stats by referrer_id
  Supabase-->>CommissionRepositoryAdapter: stats or null
  CommissionRepositoryAdapter->>Supabase: insert referral_stats if missing
  Supabase-->>CommissionRepositoryAdapter: ok
  UppromoteWebhookRoute-->>UpPromote: log Order attributed
Loading

Sequence diagram for UpPromote payoutProcessed idempotency guard

sequenceDiagram
  actor UpPromote
  participant UppromoteWebhookRoute as UppromoteWebhookRoute
  participant Supabase as Supabase

  UpPromote->>UppromoteWebhookRoute: handlePayoutProcessed(data)
  UppromoteWebhookRoute->>Supabase: select referral_stats.referrer_id by uppromote_affiliate_id
  Supabase-->>UppromoteWebhookRoute: referrer_id or null
  UppromoteWebhookRoute->>UppromoteWebhookRoute: [if no referrer_id] log warning and return
  UppromoteWebhookRoute->>Supabase: select commission_payouts.id by referrer_id,stripe_transfer_id
  Supabase-->>UppromoteWebhookRoute: existingPayout or null
  UppromoteWebhookRoute->>UppromoteWebhookRoute: [if existingPayout] log and return
  UppromoteWebhookRoute->>UppromoteWebhookRoute: validate status (processing|paid|pending)
  UppromoteWebhookRoute->>Supabase: insert commission_payouts (user_id, amount, status, stripe_transfer_id, payout_date)
  Supabase-->>UppromoteWebhookRoute: ok
  UppromoteWebhookRoute->>Supabase: update referral_stats.paid_amount when status == paid
  Supabase-->>UppromoteWebhookRoute: ok
  UppromoteWebhookRoute-->>UpPromote: log Payout processed
Loading

File-Level Changes

Change Details Files
UpPromote commission and payout webhook handlers are refactored to use the CommissionRepositoryAdapter and gain idempotency guards and stricter DB error handling.
  • Order-attributed commissions now go through CommissionRepositoryAdapter.processOrderCommission instead of inline tier/amount calculations.
  • Referral stats updates for UpPromote now use CommissionRepositoryAdapter.updateReferralStats and check Supabase update errors.
  • Payout processing adds a duplicate-guard lookup in commission_payouts using payoutId/reference and persists stripe_transfer_id, with stricter status validation and logging.
src/app/api/webhooks/uppromote/route.ts
CommissionRepositoryAdapter enforces database-level idempotency via upsert on (referrer_id, order_id) and prepares for atomic referral stats updates.
  • Replaced check-then-insert logic with a commissions upsert on conflict referrer_id,order_id, followed by a select().
  • On upsert failure, falls back to selecting the existing commission record by referrer_id and order_id.
  • Updated updateReferralStats to defensively insert missing referral_stats rows and prepare for an atomic RPC-based update.
src/lib/adapters/CommissionRepositoryAdapter.ts
Shopify product and inventory webhooks are hardened with explicit Supabase error checking on all reads and writes affecting product catalog data.
  • Product select, update, and insert operations now check Supabase error codes and throw on unexpected failures.
  • Variant upsert operations validate errors and abort processing on failure to prevent partial catalog writes.
  • Inventory update handler now checks read and write errors explicitly before cache invalidation.
src/app/api/webhooks/shopify/route.ts
Webhook and commission idempotency infrastructure is tightened via Redis TTL adjustments and new commission table metadata fields.
  • IdempotencyStoreAdapter uses a short 60-second TTL for the initial processing placeholder and logs failures when marking completion.
  • Cached status parsing and Redis get errors now emit structured warnings/errors instead of silent failures.
  • Commission database types gain webhook_id, idempotency_key, and webhook_processed_at fields to track webhook processing lineage.
src/lib/adapters/IdempotencyStoreAdapter.ts
src/types/database.types.ts
Referral and UpPromote domain models are aligned with database tier behavior and calculation rules.
  • CommissionTier and CommissionRecord types now include the custom tier and optional tier_multiplier to match DB schema.
  • UpPromote commission tier determination copies and sorts the tier rules array defensively before selecting a tier.
src/lib/referrals.ts
src/lib/uppromote.ts
The process-order commissions API and surrounding infra gain more robust request parsing and testing/tooling support.
  • Process-order route now guards against invalid JSON request bodies and removes a second request.json() call in the error path.
  • A vitest configuration file is added to wire up path aliases and manual .env.local loading for tests.
  • A new verify-quality-engine.mts script implements a CLI around the QualityEngine and integrates with CI, baselines, and severity gating.
src/app/api/commissions/process-order/route.ts
vitest.config.ts
scripts/verify-quality-engine.mts
Database migration 009 enforces unique commissions per (referrer_id, order_id) and introduces an atomic referral stats RPC.
  • Migration deduplicates existing commissions rows by keeping the earliest per (referrer_id, order_id).
  • Adds a unique index uq_commissions_referrer_order on referrer_id, order_id.
  • Defines update_referral_stats_atomic(p_referrer_id, p_commission_amount, p_order_total) to upsert and atomically increment referral_stats aggregates.
migrations/009_commission_integrity.sql
Project memory and documentation are updated to reflect Wave 1 planning, review matrices, and stabilization execution plan.
  • Agent ledger is extended with Wave 1 stabilization session entries and ownership notes for money/auth/settings/frontend waves.
  • An execution plan document details the stabilization and commerce-core roadmap, gates, and responsibilities.
  • A testing review matrix document records layout-related findings, severities, and recommended fixes for frontend components.
.memory/AGENT_LEDGER.md
.memory/EXECUTION_PLAN_2026-07-16.md
docs/testing/review-matrix.md

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: cb60afe9-88a5-4412-8312-4967f322db96

📥 Commits

Reviewing files that changed from the base of the PR and between 0c869a6 and 8a6c1c1.

⛔ Files ignored due to path filters (3)
  • docs/for_sharing/eye_lashes.png is excluded by !**/*.png
  • public/landing/avatar-farida.png is excluded by !**/*.png
  • public/landing/avatar-sarah.png is excluded by !**/*.png
📒 Files selected for processing (16)
  • .memory/AGENT_LEDGER.md
  • .memory/EXECUTION_PLAN_2026-07-16.md
  • docs/testing/review-matrix.md
  • migrations/009_commission_integrity.sql
  • scripts/verify-quality-engine.mts
  • src/app/api/commissions/process-order/route.ts
  • src/app/api/webhooks/shopify/route.ts
  • src/app/api/webhooks/uppromote/route.ts
  • src/lib/adapters/CommissionRepositoryAdapter.ts
  • src/lib/adapters/IdempotencyStoreAdapter.ts
  • src/lib/referrals.ts
  • src/lib/uppromote.ts
  • src/types/database.types.ts
  • test-results/.last-run.json
  • tsconfig.tsbuildinfo
  • vitest.config.ts
🔥 Files not summarized due to errors (1)
  • tsconfig.tsbuildinfo: Server error: no LLM provider could handle the message

Walkthrough

The PR adds commission integrity constraints and atomic referral-stat updates, routes UpPromote processing through a repository adapter, strengthens webhook error handling and idempotency, introduces a quality-engine verification CLI, adds Vitest setup, and documents execution plans and review findings.

Changes

Commission integrity and webhook processing

Layer / File(s) Summary
Commission contracts and database integrity
migrations/009_commission_integrity.sql, src/types/database.types.ts, src/lib/referrals.ts, src/lib/uppromote.ts
Commission duplicates are removed and prevented, referral statistics gain an atomic update function, commission types gain fields, and tier selection avoids mutating shared configuration.
Adapter-driven commission and payout processing
src/lib/adapters/CommissionRepositoryAdapter.ts, src/app/api/webhooks/uppromote/route.ts
Commission upserts, referral-stat creation, adapter-based order processing, and payout idempotency references are implemented.
Webhook parsing and persistence error handling
src/app/api/commissions/process-order/route.ts, src/app/api/webhooks/shopify/route.ts, src/lib/adapters/IdempotencyStoreAdapter.ts
Invalid JSON returns a client error, database failures propagate, processing reservations use a short TTL, and Redis update or parse failures are logged.

Quality verification and test setup

Layer / File(s) Summary
Quality engine CLI and gating
scripts/verify-quality-engine.mts
The CLI supports scan modes, rule adaptation, caching, normalized findings, baselines, comparisons, rule-error failures, and severity-based exit behavior.
Review findings and test environment
docs/testing/review-matrix.md, vitest.config.ts, test-results/.last-run.json
Review findings and validation results are documented, Vitest loads .env.local and the @ alias, and the previous test-run state is cleared.

Execution planning documentation

Layer / File(s) Summary
Wave orchestration and delivery plan
.memory/AGENT_LEDGER.md, .memory/EXECUTION_PLAN_2026-07-16.md
Wave execution status, workstream sequencing, open decisions, and review protocol requirements are recorded.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant UpPromoteWebhook
  participant CommissionRepositoryAdapter
  participant Supabase
  UpPromoteWebhook->>CommissionRepositoryAdapter: process order commission
  CommissionRepositoryAdapter->>Supabase: upsert commission and referral statistics
  UpPromoteWebhook->>Supabase: check payout reference
  Supabase-->>UpPromoteWebhook: existing or new payout state
Loading

Possibly related PRs

Suggested reviewers: claude, newmusicyy111

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/wave1a-commission-integrity
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch fix/wave1a-commission-integrity

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install timed out. The project may have too many dependencies for the sandbox.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@TechHypeXP
TechHypeXP merged commit dacd799 into main Jul 16, 2026
10 of 11 checks passed

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 4 issues, and left some high level feedback:

  • CommissionRepositoryAdapter.updateReferralStats currently only ensures the stats row exists and never actually updates any aggregates; wire it up to call the new update_referral_stats_atomic RPC (with the appropriate commission and order totals) or otherwise perform the atomic increments so UpPromote/order flows keep stats correct.
  • The process-order webhook route no longer calls markWebhookProcessed on errors and also removed the second body parse; consider reintroducing failure marking using the already-parsed body so idempotency semantics remain consistent even when processing fails.
  • The update_referral_stats_atomic RPC unconditionally sets volume_month_reset_at to NOW on every update, which will effectively disable month-boundary reset logic; review this so monthly volume and tier calculations continue to behave as intended (e.g., only resetting at month rollover).
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- CommissionRepositoryAdapter.updateReferralStats currently only ensures the stats row exists and never actually updates any aggregates; wire it up to call the new update_referral_stats_atomic RPC (with the appropriate commission and order totals) or otherwise perform the atomic increments so UpPromote/order flows keep stats correct.
- The process-order webhook route no longer calls markWebhookProcessed on errors and also removed the second body parse; consider reintroducing failure marking using the already-parsed body so idempotency semantics remain consistent even when processing fails.
- The update_referral_stats_atomic RPC unconditionally sets volume_month_reset_at to NOW on every update, which will effectively disable month-boundary reset logic; review this so monthly volume and tier calculations continue to behave as intended (e.g., only resetting at month rollover).

## Individual Comments

### Comment 1
<location path="src/lib/adapters/CommissionRepositoryAdapter.ts" line_range="227-233" />
<code_context>
   async updateReferralStats(referrerId: string): Promise<void> {
     const { supabaseAdmin } = await import('@/lib/db')

</code_context>
<issue_to_address>
**issue (bug_risk):** updateReferralStats currently only ensures the row exists and never applies any aggregate updates.

The new migration adds an `update_referral_stats_atomic(p_referrer_id, p_commission_amount, p_order_total)` RPC to maintain `referral_stats`, but this method only does a best‑effort insert and never calls the RPC or updates aggregates. Since callers (e.g. the UpPromote webhook order handler) now depend on `updateReferralStats` to keep `total_conversions`, `total_commission_earned`, and `volume_month` correct, those values will remain stale. Please either wire this method to the RPC (with the required inputs) or implement the aggregate updates here, and remove or update any comments that imply additional work is happening to avoid silently incomplete behavior.
</issue_to_address>

### Comment 2
<location path="migrations/009_commission_integrity.sql" line_range="23-32" />
<code_context>
+CREATE OR REPLACE FUNCTION public.update_referral_stats_atomic(
</code_context>
<issue_to_address>
**issue (bug_risk):** The atomic stats RPC ignores existing monthly reset logic and volume_ytd, which may cause long-term stats drift.

Previously, `referral_stats` updates handled month rollovers (`volume_month` reset and `volume_month_reset_at` update) and maintained `volume_ytd`. The new `update_referral_stats_atomic` only increments `total_conversions`, `total_commission_earned`, and `volume_month`, sets `volume_month_reset_at` on insert, and never updates it on conflict, so month boundaries and YTD volume are no longer enforced and `volume_month_reset_at` can become stale. If these fields are still used (e.g., tiering/reporting), the RPC should implement equivalent reset/YTD logic or those fields should be explicitly deprecated to avoid inconsistent stats.
</issue_to_address>

### Comment 3
<location path="scripts/verify-quality-engine.mts" line_range="279-281" />
<code_context>
+      console.error("❌ No baseline found. Run with --baseline first.");
+      process.exit(1);
+    }
+    const baselineFindings: {file:string;title:string}[] = JSON.parse(fs.readFileSync(baselinePath, "utf8"));
+    const baselineSet = new Set(baselineFindings.map(f => `${f.file}:${f.title}`));
+    const newFindings = findings.filter(f => !baselineSet.has(`${f.file}:${f.title}`));
+    if (newFindings.length > 0) {
+      console.error("⚠️ qa-intel: New/changed issues found:");
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Baseline comparison keys only on file and title, which can conflate different rules or instances.

The baseline key is built as "${f.file}:${f.title}", so distinct rules with the same title in a file, or multiple findings with the same title at different locations, will be merged. That can cause new/changed issues to be treated as already baselined. Consider including the rule identifier (and possibly a location such as line/column) in the key to make comparison accurate and avoid missed findings.

Suggested implementation:

```typescript
  if (compare) {
    if (!fs.existsSync(baselinePath)) {
      console.error("❌ No baseline found. Run with --baseline first.");
      process.exit(1);
    }

    const makeFindingKey = (f: {
      file: string;
      title: string;
      ruleId?: string;
      rule?: string;
      line?: number;
      column?: number;
    }): string => {
      const rule = f.ruleId ?? f.rule ?? "";
      const location =
        f.line != null && f.column != null ? `:${f.line}:${f.column}` : "";
      return `${f.file}:${rule}:${f.title}${location}`;
    };

    const baselineFindings: {
      file: string;
      title: string;
      ruleId?: string;
      rule?: string;
      line?: number;
      column?: number;
    }[] = JSON.parse(fs.readFileSync(baselinePath, "utf8"));

    const baselineSet = new Set(baselineFindings.map(makeFindingKey));
    const newFindings = findings.filter(f => !baselineSet.has(makeFindingKey(f)));

```

```typescript
    if (newFindings.length > 0) {
      console.error("⚠️ qa-intel: New/changed issues found:");
      console.error(JSON.stringify(newFindings, null, 2));
      process.exit(1);
    } else {
      console.log("✅ qa-intel: No new issues since baseline.");
      process.exit(0);
    }
  }

```

- Ensure that the `findings` objects being written to the baseline (earlier in this file) include `ruleId` or `rule` and, if available, `line` and `column`, so the comparison uses stable identifiers.
- If there is a shared `Finding` type/interface elsewhere in the codebase, update it to include `ruleId`/`rule` and location fields, then replace the inline type in `baselineFindings` and `makeFindingKey` with that shared type for consistency.
</issue_to_address>

### Comment 4
<location path="docs/testing/review-matrix.md" line_range="16" />
<code_context>
+| **REV-001** | Low | **P3** | [layout.tsx](file:///home/kellyb_dev/projects/hex-diva/src/app/layout.tsx) | Import Ordering | Framework import `next/script` is placed after type definitions. Should follow: framework → thirdparty → internal → types. | Reorder imports to group framework libraries first. |
</code_context>
<issue_to_address>
**nitpick (typo):** Consider changing “thirdparty” to the standard hyphenated form “third-party” in the import ordering description.

For clarity and consistency with common usage, consider updating the wording to use the hyphenated “third-party” in the import ordering description.

```suggestion
| **REV-001** | Low | **P3** | [layout.tsx](file:///home/kellyb_dev/projects/hex-diva/src/app/layout.tsx) | Import Ordering | Framework import `next/script` is placed after type definitions. Should follow: framework → third-party → internal → types. | Reorder imports to group framework libraries first. |
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines 227 to 233
async updateReferralStats(referrerId: string): Promise<void> {
const { supabaseAdmin } = await import('@/lib/db')

// First ensure the record exists
const { data: stats, error: statsError } = await supabaseAdmin
.from('referral_stats')
.select('*')

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): updateReferralStats currently only ensures the row exists and never applies any aggregate updates.

The new migration adds an update_referral_stats_atomic(p_referrer_id, p_commission_amount, p_order_total) RPC to maintain referral_stats, but this method only does a best‑effort insert and never calls the RPC or updates aggregates. Since callers (e.g. the UpPromote webhook order handler) now depend on updateReferralStats to keep total_conversions, total_commission_earned, and volume_month correct, those values will remain stale. Please either wire this method to the RPC (with the required inputs) or implement the aggregate updates here, and remove or update any comments that imply additional work is happening to avoid silently incomplete behavior.

Comment on lines +23 to +32
CREATE OR REPLACE FUNCTION public.update_referral_stats_atomic(
p_referrer_id UUID,
p_commission_amount DECIMAL(10, 2),
p_order_total DECIMAL(10, 2)
)
RETURNS VOID
LANGUAGE plpgsql
SECURITY DEFINER
AS $$
BEGIN

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): The atomic stats RPC ignores existing monthly reset logic and volume_ytd, which may cause long-term stats drift.

Previously, referral_stats updates handled month rollovers (volume_month reset and volume_month_reset_at update) and maintained volume_ytd. The new update_referral_stats_atomic only increments total_conversions, total_commission_earned, and volume_month, sets volume_month_reset_at on insert, and never updates it on conflict, so month boundaries and YTD volume are no longer enforced and volume_month_reset_at can become stale. If these fields are still used (e.g., tiering/reporting), the RPC should implement equivalent reset/YTD logic or those fields should be explicitly deprecated to avoid inconsistent stats.

Comment on lines +279 to +281
const baselineFindings: {file:string;title:string}[] = JSON.parse(fs.readFileSync(baselinePath, "utf8"));
const baselineSet = new Set(baselineFindings.map(f => `${f.file}:${f.title}`));
const newFindings = findings.filter(f => !baselineSet.has(`${f.file}:${f.title}`));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (bug_risk): Baseline comparison keys only on file and title, which can conflate different rules or instances.

The baseline key is built as "${f.file}:${f.title}", so distinct rules with the same title in a file, or multiple findings with the same title at different locations, will be merged. That can cause new/changed issues to be treated as already baselined. Consider including the rule identifier (and possibly a location such as line/column) in the key to make comparison accurate and avoid missed findings.

Suggested implementation:

  if (compare) {
    if (!fs.existsSync(baselinePath)) {
      console.error("❌ No baseline found. Run with --baseline first.");
      process.exit(1);
    }

    const makeFindingKey = (f: {
      file: string;
      title: string;
      ruleId?: string;
      rule?: string;
      line?: number;
      column?: number;
    }): string => {
      const rule = f.ruleId ?? f.rule ?? "";
      const location =
        f.line != null && f.column != null ? `:${f.line}:${f.column}` : "";
      return `${f.file}:${rule}:${f.title}${location}`;
    };

    const baselineFindings: {
      file: string;
      title: string;
      ruleId?: string;
      rule?: string;
      line?: number;
      column?: number;
    }[] = JSON.parse(fs.readFileSync(baselinePath, "utf8"));

    const baselineSet = new Set(baselineFindings.map(makeFindingKey));
    const newFindings = findings.filter(f => !baselineSet.has(makeFindingKey(f)));
    if (newFindings.length > 0) {
      console.error("⚠️ qa-intel: New/changed issues found:");
      console.error(JSON.stringify(newFindings, null, 2));
      process.exit(1);
    } else {
      console.log("✅ qa-intel: No new issues since baseline.");
      process.exit(0);
    }
  }
  • Ensure that the findings objects being written to the baseline (earlier in this file) include ruleId or rule and, if available, line and column, so the comparison uses stable identifiers.
  • If there is a shared Finding type/interface elsewhere in the codebase, update it to include ruleId/rule and location fields, then replace the inline type in baselineFindings and makeFindingKey with that shared type for consistency.


| Finding ID | Severity | Priority | File | Category / Rule | Description | Recommended Fix |
| :--- | :--- | :--- | :--- | :--- | :--- | :--- |
| **REV-001** | Low | **P3** | [layout.tsx](file:///home/kellyb_dev/projects/hex-diva/src/app/layout.tsx) | Import Ordering | Framework import `next/script` is placed after type definitions. Should follow: framework → thirdparty → internal → types. | Reorder imports to group framework libraries first. |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick (typo): Consider changing “thirdparty” to the standard hyphenated form “third-party” in the import ordering description.

For clarity and consistency with common usage, consider updating the wording to use the hyphenated “third-party” in the import ordering description.

Suggested change
| **REV-001** | Low | **P3** | [layout.tsx](file:///home/kellyb_dev/projects/hex-diva/src/app/layout.tsx) | Import Ordering | Framework import `next/script` is placed after type definitions. Should follow: framework → thirdparty → internal → types. | Reorder imports to group framework libraries first. |
| **REV-001** | Low | **P3** | [layout.tsx](file:///home/kellyb_dev/projects/hex-diva/src/app/layout.tsx) | Import Ordering | Framework import `next/script` is placed after type definitions. Should follow: framework → third-party → internal → types. | Reorder imports to group framework libraries first. |

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.

2 participants