Skip to content

fix(credits): atomic Stripe webhook fulfillment + idempotency + privilege guard#2

Open
kimhons wants to merge 106 commits into
mainfrom
feat/asset-system-regen
Open

fix(credits): atomic Stripe webhook fulfillment + idempotency + privilege guard#2
kimhons wants to merge 106 commits into
mainfrom
feat/asset-system-regen

Conversation

@kimhons
Copy link
Copy Markdown
Contributor

@kimhons kimhons commented May 1, 2026

Summary

Resolves 5 CRITICAL/HIGH findings from the 2026-05-01 team-review of the credit fulfillment path.

What changed

  • New add_credits RPC (supabase/migrations/20260501000000_credit_purchase_idempotency.sql): single Postgres transaction over the credit_purchases insert, user_credits UPDATE, and credit_transactions log. Idempotency check inside the function via stripe_payment_intent_id — duplicate Stripe webhook deliveries return already_fulfilled=true with no double-grant and no stranded sentinel row.
  • Privilege escalation closed: REVOKE EXECUTE ... FROM PUBLIC; GRANT EXECUTE ... TO service_role on both add_credits and deduct_credits. Previously, any authenticated PostgREST caller could credit/debit any user.
  • UNIQUE NOT NULL on credit_purchases.stripe_payment_intent_id: hard backstop for the idempotency check; backfill DELETE for any pre-existing NULLs.
  • credit_transactions INSERT policy scoped TO service_role (was open to all authenticated clients via WITH CHECK (true)).
  • stripe-service.ts: handlePaymentIntentSucceeded now logs then rethrows so Stripe retries on transient failures (was silently swallowing → permanent credit loss). fulfillCreditPurchase collapses to a single RPC call.
  • credit-service.ts: addCredits rewritten to call the atomic RPC; returns {newBalance, alreadyFulfilled}. New optional fulfillment param wires the Stripe metadata into the same transaction.
  • api/credits/purchase/route.ts: switched from PaymentIntent to Checkout Session (mode='payment') and returns checkoutUrl.
  • CreditPurchaseModal.tsx: removed dead data.newBalance / onPurchaseComplete logic; now redirects to Stripe-hosted Checkout. The previous PaymentIntent flow was never wired (modal read fields the route never returned).

Issues closed (all from team-review #d64e8d5 ancestor)

Severity Finding Status
CRITICAL Stripe webhook double-credit on retry FIXED
CRITICAL Webhook insert wrong column names (amount_centsamount_paid_cents, dropped phantom status) FIXED
CRITICAL addCredits non-atomic read-modify-write FIXED
HIGH CreditPurchaseModal reads fields route never returns → unwired FIXED
HIGH credit_transactions INSERT RLS missing role scope FIXED
HIGH (re-review) Webhook errors swallowed → no Stripe retry FIXED
HIGH (re-review) Insert-then-RPC split transaction → stranded row FIXED
HIGH (re-review) Supabase error code field uncertainty OBVIATED (single-RPC path)
MEDIUM (re-review) UNIQUE on nullable column allows multiple NULLs FIXED
MEDIUM (re-review) SECURITY DEFINER functions callable by authenticated FIXED

Test plan

  • tsc --noEmit exit 0
  • next build exit 0 (539 kB first load)
  • Jest: 14,568 / 14,587 passing (19 environment-skipped, 0 failed). Added 7 new tests covering: atomic RPC happy path, fulfillment payload forwarding, alreadyFulfilled=true short-circuit, non-array RPC response, non-positive amount guard, RPC error propagation, empty-result guard.
  • Dev server proof-of-life: HTTP 200 on /.
  • Re-review by code-reviewer + security-reviewer: PASS on all originally-flagged items.

Out-of-scope follow-ups (do NOT block merge)

  1. HIGHstripe-service.ts:644 handleInvoicePaid swallows errors on the subscription-renewal credit-reset path. Same pattern as the bug fixed here for credit purchases. Pre-existing; not in original team-review scope.
  2. MEDIUMstripe-service.ts:685 handleInvoicePaymentFailed missing log on catch.
  3. LOWadd_credits idempotency SELECT runs before FOR UPDATE lock. UNIQUE constraint is the hard backstop; a true concurrent race surfaces as one extra Stripe retry rather than silent skip. Could tighten with INSERT ... ON CONFLICT DO NOTHING RETURNING id.
  4. LOWresetMonthlyAllowance writes to credit_transactions via the admin client, relying on caller-side privilege rather than schema. Architectural note.

Breaking changes

  • creditService.addCredits return type changed from Promise<number> to Promise<{ newBalance: number; alreadyFulfilled: boolean }>. Sole caller (stripe-service.ts) updated in this PR.
  • CreditPurchaseModal onPurchaseComplete prop removed (unused; the modal now always redirects to Stripe-hosted Checkout).

kimhons added 30 commits April 16, 2026 05:35
Critical (3):
- SEC C-1: pin axios>=1.15.0 via overrides (CVSS 10.0 SSRF chain)
- SEC C-2: pin handlebars>=4.7.9 via overrides (CVSS 9.8 JS injection)
- ARCH C-1: consolidate three Spinner implementations to one canonical
  in Loading.tsx; barrel re-exports from LoadingSkeleton

High (15):
- SEC H-1/H-2: tighten RLS on adverse_action_notices and consent_records
  to TO service_role; users keep SELECT-only on consent
- SEC H-3: bump next to ^15.5.15 (resolves H-4 undici)
- SEC H-5: replace document.write in BackupCodesManagement with DOM API
- SEC H-6: pin lodash>=4.17.24 via overrides
- CQ H1/H2/H3, SEC M-1/M-4, ARCH H2: rewrite GDPR deleteUserData as an
  atomic Postgres RPC (delete_user_data_cascade), nullable user_id
  anonymization, system-scoped audit log entries, error-checked auth
  user deletion, DbClient injection across GDPR/CCPA services
- ARCH H3: convertToCSV / convertToXML throw NotImplementedError
- CQ H4: remove function db(): any helper
- ARCH H1: 4 email templates use shared EmailFooter (CAN-SPAM-compliant)
- ARCH H4: 15 credit-builder loading.tsx use shared LoadingPage
- ARCH H5: shared brand-colors.ts primitives; chart-colors.ts and
  email-colors.ts import from it

Other:
- Add minimal app/not-found.tsx so production build succeeds with
  NODE_ENV=production (build prereq for /verify proof-of-life)
- Bump direct undici dep to ^7.24.0
- /verify breadcrumb at .claude/last-verification.json

Verification: lint clean, types clean, build OK (438 routes, 538 kB
shared JS), tests 13615/13634 passing, audit 0 critical / 0 high,
proof-of-life GET / -> 200 in 62ms on :3100.
3-wave plan to generate the full Fynvita asset system using Google's
Nano Banana (Gemini 2.5 Flash Image) as the generation engine, with
existing brand guidelines as ground truth.

- Pipeline: TOML spec -> @google/genai -> raster -> VTracer -> SVGO ->
  production registry -> per-platform destinations
- Wave 1: brand mark, app icons (iOS/Android/web), splash, favicon/PWA
- Wave 2: empty states (15), onboarding, success/celebration, 404
- Wave 3: hero, OG images, App Store + Play Store marketing
- 4 user-review gates between waves
- ~255 API calls, ~$20-30 total

Also adds GEMINI_API_KEY placeholder to .env.local.example.

Plan: docs/superpowers/plans/2026-04-16-fynvita-asset-system-regen.md
- Copy 7 approved PNGs from assets/raw/logo-system/ to assets/production/logo/
- Rename with standardized production naming (fynvita-horizontal, -vertical, -mark, etc.)
- Create comprehensive README.md with usage guide, brand colors, and minimum sizes
- Create MANIFEST.json with file metadata (dimensions, bytes, colors, usage)
- Total: 8.7 MB of production logo assets ready for Wave 1.3 (app icons)
- Add assets/specs/splash-source.toml (Nano Banana Pro generation spec)
- Add scripts/assets/derive-splash.ts (164 lines) using sharp
- Produce assets/production/splash/splash-1284x2778.png + splash-1024x1024.png
- Replace mobile-app/assets/splash.png (1284x2778, Vital Green bg + mark)
- Replace mobile-app/assets/icon.png + adaptive-icon.png (1024x1024)
- Replace mobile-app/assets/favicon.png (48x48) + notification-icon.png (96x96)
- Update mobile-app/app.config.js: backgroundColor #3B82F6 → #10B981 (3 sites)
- Add assets:derive-splash npm script to package.json
- Copy 7 logo PNGs to public/brand/ (horizontal, vertical, mark,
  mark-mono-navy, reversed, wordmark, app-icon)
- Copy favicon.ico + apple-touch-icon.png to public root
- Copy PWA icon set to public/brand/ (pwa-192/512, maskable variants,
  favicon-16/32/48)
- Add public/manifest.webmanifest with corrected /brand/ icon paths
- Add src/components/brand/registry.ts — central BrandAsset registry
  with precise aspect ratios from sharp metadata (horizontal: 2816/1536)
- Add src/components/brand/BrandMark.tsx — Server Component compatible,
  height-driven sizing, decorative + priority props
- Add src/components/brand/index.ts — barrel export
- Add src/components/brand/__tests__/BrandMark.test.tsx — 14 passing
- src/components/ui/Header.tsx: replace F-box+text lockup in desktop
  nav and mobile menu panel with BrandMark (horizontal/36 + horizontal/32)
- src/components/auth/LoginForm.tsx: replace F-box+text with
  BrandMark (vertical/80, priority)
- src/components/auth/SignUpForm.tsx: replace F-box+text with
  BrandMark (vertical/80, priority)
- src/components/auth/ResetPasswordForm.tsx: replace gradient text
  'Fynvita' with BrandMark (vertical/80, priority)
- src/app/layout.tsx: add title template, manifest link, and icons
  metadata (favicon-16/32/48, apple-touch-icon)
…, compliance

- Add shared Button component with 4 variants, 4 sizes, loading state,
  focus rings, and TypeScript-enforced aria-label for icon buttons
- Add 17 loading.tsx skeleton files for all route groups (100% coverage)
- Extract chart colors to design tokens (chart-colors.ts)
- Extract email colors to design tokens (email-colors.ts)
- Add FCRA adverse action notice service with full test coverage
- Add adverse action notices database migration

Tests: 13,629 passed, 0 failures (20 new Button tests + 3 compliance tests)
Phase 1 — Animation Infrastructure:
- Animation variants library with fade, scale, slide, stagger presets
- FadeIn, StaggerList, ScrollReveal, AnimatedNumber components
- Page transition via template.tsx (fade+slideUp on route change)
- useReducedMotion hook for prefers-reduced-motion

Phase 2 — Micro-Interactions:
- AnimatedSwitch with spring-interpolated thumb
- AnimatedTabs with sliding layoutId underline
- InteractiveCard with hover lift and press feedback
- Button press feedback (whileTap scale 0.97)
- Toast spring physics enter/exit with AnimatePresence

Phase 3 — Trading Charts:
- Drawing tools engine (trendline, horizontal, fibonacci)
- DrawingToolbar and DrawingOverlay (SVG over lightweight-charts)
- Volume profile calculation and overlay component
- OrderBook ladder display with animated row updates
- DepthChart cumulative bid/ask visualization
- useRealtimeChart hook with 4/sec throttled updates

Phase 4 — Asset States:
- Confetti particles (40 framer-motion divs, auto-cleanup)
- CelebrationOverlay with self-drawing SVG checkmark
- useConfetti imperative hook
- AnimatedErrorState with 5 animated SVG variants
- ChartLoadingSkeleton with animated sine-wave
- 4 new EmptyState illustrations (investments, goals, alerts, getting-started)

Phase 5 — Page Polish:
- Dashboard, landing, credit, investments, trading, analytics,
  notifications pages with ScrollReveal and StaggerList
- Onboarding step transitions via FadeIn key={pathname}
- DragReorder component using framer-motion Reorder API

Tests: 13,730 passed, 0 failures (+101 new tests)
Types: 0 errors | 45 files changed, 4,319 insertions
- Generate 8 PWA icon sizes (72-512px) from pwa-512.png source
- Generate dashboard and dispute shortcut icons (96px)
- Fix manifest.json: all /icons/ paths now resolve to real files
- Add maskable icon entries from /brand/ to manifest.json
- Align manifest.webmanifest with manifest.json

Resolves: 13 missing asset references in web manifest
kimhons and others added 29 commits April 27, 2026 15:49
…ntegration

- GET /api/credits/balance: returns credit balance and usage stats
- GET /api/credits/history: paginated transaction history
- POST /api/credits/purchase: creates Stripe PaymentIntent for credit packs
- POST /api/addons/subscribe: subscribes to addon bundles via Stripe
- POST /api/addons/cancel: cancels addon subscriptions
- GET /api/addons/list: lists active addon subscriptions
- credit-reset.ts: resets credits for tier + active addon credits
- webhook: payment_intent.succeeded handles credit pack fulfillment
- webhook: invoice.paid triggers monthly credit reset per tier
…, tests

Web components:
- CreditBalance: compact header + expanded settings display with progress bar
- CreditPurchaseModal: 3 pack cards with purchase flow
- LowCreditBanner: dismissible warning when credits < 20%
- CreditUsageHistory: paginated transaction table with action icons
- Settings credits page: full credit management dashboard

Mobile:
- credits.tsx settings screen with balance, purchase, history
- creditBalanceStore.ts: Zustand store with fetchBalance, purchasePack, selectors
- Updated store index and settings layout

Pricing page:
- Added 'X credits/mo' line with icon to each tier card

Tests (41 passing):
- credit-costs.test.ts: 19 cases covering packs, tiers, costs, estimates
- credit-service.test.ts: 22 cases covering getBalance, deductCredits,
  addCredits, checkSufficientCredits, resetMonthlyAllowance,
  getTransactionHistory, getUsageThisPeriod
… enhanced typography

- Add DeviceMockup component (LaptopFrame, PhoneFrame, DashboardMockup, MobileMockup)
- Convert hero to 2-column grid with floating device composition on lg+
- Add 'See It In Action' product showcase section with web + mobile side-by-side
- Increase hero heading to text-5xl/6xl/7xl responsive scale
- Add float/float-slow keyframe animations to Tailwind config
- Apply glassmorphism (backdrop-blur-sm, bg-white/60) to all badge labels
- Add emerald glow hover effect (hover:shadow-emerald-500/25) to primary CTAs
- Increase Product Grid section spacing from py-4 to py-24
- Increase Features heading from text-2xl to text-4xl/5xl
- Add subtle gradient background overlay to hero section
- All changes preserve existing dark mode, responsive design, and functionality
- Reduce hero h1 to text-3xl on mobile (was text-5xl, overflowed 375px viewport)
- Fix build script: explicitly set NODE_ENV=production to resolve
  conflicting values warning that caused /404 prerender failure
- Add .playwright-mcp/ to .gitignore
…st-name-only

- Fix AI Technology stat cards: bg-white → bg-white/10 (white text was
  invisible on opaque white background)
- Fix AI Capabilities cards: same bg-white → bg-white/10 fix
- Fix mobile app section: bg-white → bg-white/15, add credit score card
- Fix pricing cards: remove overflow-hidden, reduce xl font to text-2xl
  (prices were being clipped at card boundaries)
- Update all 7 testimonials to first names only (Sarah, Michael, Emily,
  David, Lisa, James, Amanda)
…ders

- Generate 3 photorealistic device mockups via Google Imagen 4.0:
  hero-devices.png (MacBook Pro + iPhone 15 Pro, 16:9),
  showcase-laptop.png (MacBook Pro dashboard, 16:9),
  mobile-phone.png (iPhone 15 Pro portrait, 9:16)
- Replace hero CSS DeviceMockup components with hero-devices.png Image
- Replace 'Beautiful on every device' section with showcase-laptop.png
- Replace 'Fynvita in your pocket' CSS phone with mobile-phone.png
- Remove unused DeviceMockup import (components kept for reference)
…endations

Regenerated all 3 Imagen 4.0 mockups with detailed app previews:
- Hero: Credit Score Trend area chart, ,250 net worth, ,840 savings,
  23% debt ratio cards, AI Recommendations panel (dispute, credit limit,
  portfolio rebalance), mobile score ring + spending donut
- Showcase: 6-Month Financial Health Trend chart, AI Agent Insights panel
  (3 recommendations), bureau scores with sparklines, spending donut
- Mobile: 731 score ring, AI Recommendations (dispute 94%, savings +3%,
  rebalance +2.1%), spending category bars, tab navigation

Users now see a realistic preview of account analysis and agentic AI
features before signing up.
…cleaner text

- Hero: all three key metrics now render correctly (731, $47,250, $1,840)
  with clean dark dashboard UI and green area chart
- Mobile: 731 score with +18 indicator, clean recommendation cards with
  colored icons and badges, spending bars at bottom
- Showcase: unchanged (already clean — 731, $47,250, $1,840, +12.4%)
- Switched to Imagen 4.0 Ultra for sharper text and detail
- Simplified prompts to focus on visual elements over body text
- Hero: full laptop + iPhone visible (no cropping), complete score ring,
  single smooth S-curve chart with green gradient fill
- Showcase: full laptop visible, single ascending curve (replaces
  nonsensical overlapping multi-series chart), correct bureau scores
  728/735/731 with donut chart
- Both retain correct key numbers: 731, $47,250, $1,840, +12.4%
- iPhone screen now faces the viewer (was showing camera/back side)
- Laptop sidebar branded 'Fynvita' (was hallucinating 'Spy Pro')
- All key numbers retained: 731, $47,250, $1,840
Replace all 3 Imagen 4.0 PNG mockups with React component-based renders:

DeviceMockup.tsx — complete rewrite:
- LaptopFrame: perspective tilt, chin, glass glare, environmental shadow
- PhoneFrame: iPhone 15 notch, side button, edge glare
- LaptopScreenMockup: dark sidebar (Fynvita branding), KPI strip
  (Net Worth $47,250, Score 731, Savings $1,840), SVG chart (640→731),
  AI Recommendations (3 items), Spending vs Budget bars, bureau scores
- MobileScreenMockup: gradient header, score ring 731, quick actions,
  AI insight card, upcoming bills, tab bar

page.tsx: swap all 3 Image tags with component mockups.
Delete public/mockups/*.png (AI-generated images).

Zero typos. Perfect branding. Mathematically correct charts.
Phone was stretching to fit all content, making it unnaturally tall.
Fixed by adding aspect-ratio: 9/19.5 on the screen container with
overflow-hidden to clip content at the correct iPhone proportions.
Replace CSS component hero mockup with photorealistic composite showing
MacBook Pro + iPhone on wooden desk with natural lighting. The device
screens display the pixel-perfect Fynvita dashboard (credit score 731,
$47,250 net worth, AI recommendations, spending chart).

Showcase and mobile sections retain HTML/CSS component mockups.
…lege guard

- New add_credits RPC: single transaction over credit_purchases insert,
  user_credits update, credit_transactions log. Idempotency check inside
  the function via stripe_payment_intent_id; duplicate webhook deliveries
  return already_fulfilled=true with no double-grant and no stranded row.
- UNIQUE(stripe_payment_intent_id) NOT NULL on credit_purchases.
- REVOKE EXECUTE on add_credits + deduct_credits FROM PUBLIC; GRANT to
  service_role only - closes PostgREST privilege-escalation vector.
- credit_transactions INSERT policy scoped TO service_role.
- Stripe webhook handler propagates errors so Stripe retries on transient
  failures; previously errors were silently swallowed and credits lost.
- Switch credit-pack purchase from PaymentIntent to Checkout Session;
  modal redirects to Stripe-hosted checkout (the previous client-side
  PaymentIntent flow was never wired and rendered nothing on Buy).

Closes 5 CRITICAL/HIGH issues from the 2026-05-01 team-review.

Confidence: high
Scope-risk: narrow
Not-tested: live Stripe webhook end-to-end (covered at service layer)
Comprehensive 9-domain team review surfaced 33 CRITICAL + ~50 HIGH
findings that the 13,585-test pass rate did not catch. Re-baselining
canon docs to reflect actual security/compliance posture and add a
Wave 7 plan to close the gaps before Wave 8+ work resumes.

New:
- docs/ssot/gap_analysis.md (FND-001..FND-071, 9-theme rollup)

Updated:
- SSOT.md (VERSION-013 banner, new section 19 Audit Findings)
- MASTER-IMPLEMENTATION-PLAN.md (Wave 7: ~55 tasks across 7 phases)
- health_metrics.md (per-domain scorecard, web flipped to RED)
- version_history.md (VERSION-013 entry, re-baseline rationale)
- build_order_blueprint.md (GATE-7 hard gate, Wave 7 sequencing)
- system_blueprint.md (5-layer security intended-vs-actual)
- traceability_matrix.md (audit overlay reopening 6 tasks)
- CLAUDE.md (status banner, scorecard, Wave 7 references)

Wave 7 phases: PRE (branch hygiene incl. TASK-PRE-06 for
feat/asset-system-regen), AUTH (RBAC source of truth), WBH (Stripe
webhook hardening incl. TASK-WBH-07 subscription tier backfill),
MNY (money/idempotency), MOK (mocks-in-prod removal), CMP (compliance),
MOB (mobile secrets), IDR (IDOR sweep).

Pre-launch (no real users yet) means no GDPR Art. 33 / CCPA disclosure
obligation triggered, but findings still block GA.

Refs: team-review (security/architecture/code), team-planning synthesis
QA team review of VERSION-013 commit bc56ae8 found 5 plan-level CRITICALs.
This commit closes them all without changing app code.

Amendments:
1. Wave 7 Test-Class Requirements table (723 regression tests across 7
   classes: negative-auth 568, idempotency 13, money-cents 13, DB-required
   40, compliance corpus 47, mobile-prod-bundle 12, IDOR 30). Each phase
   exit gate now requires its named test class.
2. Map 9 unmapped CRITICALs:
   - FND-006/041..044/049..051 → explicit AUTH-03 sub-batches a-f
   - FND-031/032 → new TASK-INV-W7-01/02 (math + magic constants)
3. Reconcile HIGH count: '~50 HIGH' → '38 HIGH' across CLAUDE.md, SSOT.md,
   gap_analysis.md, health_metrics.md, traceability_matrix.md,
   build_order_blueprint.md.
4. TASK-PRE-07 — security-scoped re-review of 92 prior commits on
   feat/asset-system-regen (SEC sign-off per commit touching auth/payment/
   commerce/middleware/migrations).
5. TASK-AUTH-04-staging — synthetic monitoring (webhooks + signup + login +
   OAuth) green for 24h before deny-by-default middleware ships to prod.
6. Strike 'DONE 125 100%' from CLAUDE.md §11, MASTER-IMPLEMENTATION-PLAN.md
   §11, health_metrics.md §8.5; replace with NEEDS_VERIFICATION + Wave 7
   breakdown.

Bonus fixes:
- TASK-MOB-01..07 (Wave 7) renamed TASK-MOB-W7-01..07 to fix Wave 4 ID
  collision (Wave 4 same IDs are different tasks: Biometric, UX Polish, etc.)
- Wave 7 task count corrected: '~55' → '59' (added 4 above)
- Phase count corrected: '7' → '8' (Phase 0 through Phase 7)
- Exit Criterion #1 rewritten as explicit FND list (not the false range
  'FND-001..FND-068 critical-tagged')
- AUTH-03 split into a-f sub-batches so it doesn't deadlock downstream phases
- IDR-02..05 may begin against manual list without waiting on IDR-01

Refs: /team-qa verdict (plan critic FAIL, FND spot-check 8/8 confirmed,
test-coverage analyst recommended 723-test regression spec)
Re-ran lint, type-check, tests, build, and npm audit on
feat/asset-system-regen @ 2877317. Five gates regressed since
VERSION-013; the prior commit message claim '14,568/14,587 baseline
still valid' was incorrect.

Re-baseline results (VERSION-014):
- Tests: 35 fail / 14,533 pass / 19 skip / 14,587 total across 2
  PCTT suites (pctt-trading-service.test.ts, pctt-mode-integration.test.ts)
- Types: 0 errors (clean)
- Build: PASS, 560 kB shared first-load JS, 294 API routes / 204 pages
- Lint: 15 errors, 2,858 warnings (was 7 / 841 — REGRESSION)
- npm audit (all): 14 vulns (1 high, 11 mod, 2 low)
- npm audit (prod): 9 moderate (was 0 — REGRESSION)
  - nodemailer SMTP injection via next-auth (GHSA-vvjj-xcjg-gr5g)
  - postcss XSS via next (GHSA-qx2v-qp2m-jg93)
  - uuid via svix→resend (GHSA-w5hq-g745-h8pq)

Files:
- docs/ssot/health_metrics.md: VERSION-014 banner; §1–§5 re-baselined;
  regression log appended
- .claude/last-verification.json: verdict FAIL with per-gate detail
  and explicit invalid-prior-claim entries

Validated by critic agent (APPROVE WITH NOTES). One factual
correction applied: 35 failures span 2 PCTT suites, not 1.

Ship: BLOCKED until Wave 7 closes AND PCTT regression + production
npm audit chain are resolved.

Closes acceptance §1 of TASK-PRE-01 (npm test/tsc/build/audit re-run
+ commit). Remaining acceptance items deferred:
- gap_analysis.md regeneration (adds new findings — QA review)
- CI badge wiring for machine-generated health_metrics.md (DEVOPS)
- SSOT.md banner update (separate edit, not in scope of this run)
The mode switch in POST /api/disputes/generate returned handler
promises without awaiting them, so a rejection from the AI or
strategy handler escaped the route's try/catch as an unhandled
rejection instead of a 500 response. Await each switch arm so the
outer catch handles failures.

Adds integration coverage for the route: auth, credit, validation,
all three modes, and the error path (route 100% lines / 87% branches).

Confidence: high
Scope-risk: narrow
Adds 13 unit/integration suites (~308 tests) for previously
untested or thinly tested domains surfaced by a coverage audit:
webauthn auth routes, payment webhook + billing, dispute service,
credit-builder, credit-builder-loan, rent-reporting, goal services,
spending analyzer, and the connector registry.

Also loosens CreditBuilderService.analyzeUtilization to accept
Omit<CardUtilization, "status">[] — the method computes status
itself, so requiring it as input was a latent type error.

Confidence: high
Scope-risk: narrow
Adds scripts/check-changed-coverage.js — a line-level diff coverage
gate enforcing >=85% on added/modified executable lines (not whole
files, so one-line edits to legacy files are not penalized). Wired
as `npm run test:coverage:changed` and documented in a new rule
.claude/rules/04-coverage.md.

Fixes .claude/hooks/post-edit-lint.sh: it ran bare `npx eslint`,
which crashes on this project's legacy .eslintrc under ESLint 9.
Next.js projects now lint via `next lint --file`, matching how
`npm run lint` works.

Confidence: high
Scope-risk: narrow
Approved design for sequencing Wave 7 remediation into a bounded MVP
via workflow vertical slices, with two milestones (closed beta →
public launch). Defines MVP scope (Auth, Payments, Investments,
Financial, Credit, Mobile, Ancillary), deferral discipline for
Trading/Commerce/white-label (preserved, flag-gated, Wave 8), the
foundation block, per-vertical CRITICAL/HIGH mapping, and gate
criteria.

Confidence: high
Scope-risk: narrow
Fixes 4 blocking + 8 should-fix issues from spec review:
- C1: Appendix B uses the master plan's explicit 32-item CRITICAL
  list; documents the SSOT 32-vs-33 off-by-one for TASK-PRE-01
- C2: identifies orphaned HIGHs (FND-035/039/040/045) with no Wave 7
  task; mandates task creation
- C3: removes invented TASK-TRD-W7-00; PCTT failures are an M1
  blocker, not Wave 8
- C4: deferred-code compile-safety becomes an owned obligation
- adds Money Correctness track (Phase 3) — FND-024-027 are mandatory
  CRITICALs even though Commerce workflow is deferred

Confidence: high
Scope-risk: narrow
Bite-sized TDD plan for the Wave 7 prerequisites and auth/RBAC
rebuild — the unblocking foundation for all MVP verticals. Covers
PRE-01..07 and AUTH-01..12 (incl. AUTH-03 sub-batches a-f and
AUTH-04-staging), grounded in a map of the current auth code.
Closes 13 CRITICAL + 7 HIGH findings.

Confidence: high
Scope-risk: narrow
kimhons added a commit that referenced this pull request May 17, 2026
The admin/auth GET handler ran an independent cookie-token auth
(sb-access-token -> supabase.auth.getUser) and re-derived role from
profiles AFTER the withRole("admin") guard had already verified identity
and resolved the role from the trusted profiles table. That double-auth
is exactly the inline auth AUTH-03a Step A removes.

Collapse the handler to use the guard's AuthedUser param directly:
return { isAdmin: role is admin/super_admin, user }. Drops the cookies()
read, the inner getUser call, and the profile re-query.

Adds positive-path coverage (authenticated admin -> { isAdmin: true,
user }) which the old cookies()-dependent handler left untested, plus a
super_admin case. Rewires the two admin/auth test suites off the removed
supabase/next-headers machinery.

Addresses review MEDIUM #2 and NIT #5.

Constraint: behavior-preserving except removal of the redundant auth pass
Confidence: high
Scope-risk: narrow
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