Skip to content

Lift Lab unification → helm, CRM fixes, admin badge, e2e stabilization, Bridge incident fixes - #768

Merged
njrini99-code merged 4 commits into
mainfrom
feat/liftlab-helm-unification
Jul 3, 2026
Merged

Lift Lab unification → helm, CRM fixes, admin badge, e2e stabilization, Bridge incident fixes#768
njrini99-code merged 4 commits into
mainfrom
feat/liftlab-helm-unification

Conversation

@njrini99-code

Copy link
Copy Markdown
Owner

Lift Lab unification: program-builder → helm, CRM fixes, admin badge, e2e stabilization

One train, one deploy. Four workstreams:

1. Program-builder → helm_lifting_* (completes the Lift Lab schema split)

The last writers of the 16 legacy baseball_lift_* / baseball_strength_* / bodyweight / availability tables move to helm_lifting_* (all legacy tables verified 0 rows in prod; 15/16 had exact helm mirrors, the 16th — helm_lifting_group_audit — was created + RLS'd in prod ahead of this PR):

  • All program CRUD (createLiftProgram/addLiftWeek/addLiftDay/addLiftSection/addLiftPrescription, update/delete/reorder/duplicate/template) now writes helm tables with the family renames (player_id→athlete_id, org+sport scoping, baseball_context→sport_context).
  • publishLiftDay no longer materializes legacy rows and bridges — it writes helm_lifting_program_assignments/sessions/session_exercises directly. The legacy dual-space session resolver is deleted.
  • Strength groups/members/audit, maxes, PRs, bodyweight, availability → helm equivalents. Upserts target the real prod uniques (weeks program_id,week_number; days week_id,day_number; members group_id,athlete_id; sessions program_assignment_id,athlete_id; bodyweight athlete_id,entry_date).
  • Residual legacy reads in 10 read-model/loader files swapped; dead createLiftExercise + getStrengthGroupDetail deleted.
  • After this deploys: phase-3 graveyard migration (20260704090000, staged in this PR) moves all 16 legacy tables out of public — every FK edge stays inside graveyard (verified against pg_constraint).

2. CRM fixes (RLS already applied to prod; code here)

  • demo_requests had no SELECT/UPDATE policy → the admin Inbound Leads list showed 0 while the dashboard badge counted 3 (a real Heidelberg lead sat invisible since June 11). Admin-gated policies applied + mirrored.
  • Inbound Leads view used status values the CHECK constraint rejects (new/converted) — now mapped to the real domain (pending/completed); Add-to-CRM no longer silently fails.
  • get_crm_events_in_range + get_crm_time_to_open were EXECUTE-able by any authenticated user → admin-gated (applied + mirrored).

3. /admin/teams/[id] health badge

Detail page fed classifyTeamHealth only completed rounds by current roster members; /admin/golf uses unfiltered team activity. Detail now exposes teamLastActivity (unfiltered, one cheap query) for the badge; per-player roster columns unchanged.

4. e2e stabilization (root causes from CI-log forensics — no local browser runs)

  • The recurring red gate: seed-baseball-demo.ts never reset existing CI demo passwords, so secret drift + Playwright retries fed the 10-attempt DB lockout every run. Seed now force-sets the two synthetic CI identities' passwords and clears lockout state.
  • Fail-fast secrets verification step in playwright.yml (missing secret now fails in seconds, not after build+seed).
  • Fixed wrong post-login URL patterns (auth.spec.ts, helpers/auth.ts), tolerant waitForPageLoad, 18 hard sleeps → web-first assertions, golf specs migrated to env-gated fixtures, dead placeholder specs deleted.
  • Note: the main suite step remains soft-failed (advisory) — flipping it to a hard gate is a follow-up once a few honest green runs establish ground truth.

Verification

  • tsc 0 / eslint 0 (max-warnings 0 baseline) / full unit suite green — exit codes captured explicitly, no piped gates
  • pgTAP RLS suites updated (group-audit → helm table; v11 visibility → helm)
  • Bridge integration tests rewritten against direct-helm behavior (same protective properties: no session loss on republish, snapshot correctness)

DB sequencing (for the record)

Applied to prod pre-merge (all additive/verified-safe with live code): demo_requests_admin_read_update, crm_rpcs_admin_gate, helm_lifting_group_audit. Applied post-deploy: graveyard_legacy_liftlab_tables_phase3 (16 tables, public 275→259, graveyard 16→32).

🤖 Generated with Claude Code

https://claude.ai/code/session_01KsHxAKSPhqgfJKTFfpjRiC

Fable Integrator and others added 4 commits July 3, 2026 11:36
…s statuses to CHECK domain

demo_requests had only a public INSERT policy and a service_role ALL policy,
so the admin Inbound Leads list (browser client, RLS) rendered 0 rows while
the dashboard badge (server rollup, service role) counted 3. Adds admin-gated
SELECT/UPDATE policies (applied to prod via MCP; file mirrored).

The view also used status values the CHECK constraint rejects: filters
expected 'new'/'converted' but the form inserts 'pending', and Add-to-CRM
wrote 'converted' which demo_requests_status_check refuses — now mapped to
the real domain (pending→New, completed→Added).

Also stages the phase-3 graveyard migration (16 legacy lift/strength tables,
apply AFTER the unification deploy).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KsHxAKSPhqgfJKTFfpjRiC
Both SECURITY DEFINER functions were EXECUTE-able by any authenticated user
with no role check, exposing CRM calendar events and outreach analytics to
coaches/players. Applied to prod via MCP; file mirrored for fresh databases.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KsHxAKSPhqgfJKTFfpjRiC
…adge fix + e2e stabilization

All 6 build lanes complete and self-verified (tsc/lint clean per lane):
- lifting-v11.ts + group-audit-writer.ts fully helm-native; publishLiftDay
  writes helm directly (dual-write bridge deleted); dead createLiftExercise
  removed; audit → new helm_lifting_group_audit (applied to prod)
- 10 read-model/loader files: residual legacy reads swapped; dead
  getStrengthGroupDetail removed
- lifting.ts/program-settings.ts/lift-builder.ts + builder/live pages swapped
- demo seed + coverage scripts helm-native
- /admin/teams/[id] health badge: unfiltered teamLastActivity (36/36 tests)
- e2e: CI demo-account lockout root-cause fixed (seed force-resets password +
  clears lockouts), fail-fast secrets step, login URL patterns, waitForPageLoad,
  18 hard sleeps → web-first assertions, dead placeholder specs deleted
- staged migrations: phase-3 graveyard (apply AFTER deploy), group-audit mirror

Test-reconciliation + Bridge incident fixes land in follow-up commits on this
branch. Gate before merge.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KsHxAKSPhqgfJKTFfpjRiC
…ation, revalidate split, feed noise, log-event hardening, dynamic bails + RLS tripwire

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KsHxAKSPhqgfJKTFfpjRiC
@vercel

vercel Bot commented Jul 3, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
helmv3 Ignored Ignored Preview Jul 3, 2026 4:31pm

Request Review

@greptile-apps

greptile-apps Bot commented Jul 3, 2026

Copy link
Copy Markdown

Too many files changed for review. (70 files found, 50 file limit)

Bypass the limit by tagging @greptile-apps to review.

@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 7721a72e-d037-4d14-b5cf-4b34e0e58569

📥 Commits

Reviewing files that changed from the base of the PR and between ea90568 and 0d6c25d.

⛔ Files ignored due to path filters (1)
  • src/lib/types/database.ts is excluded by !src/lib/types/database.ts
📒 Files selected for processing (69)
  • .github/workflows/playwright.yml
  • e2e/auth.spec.ts
  • e2e/course-library.spec.ts
  • e2e/discover.spec.ts
  • e2e/golf-dashboard.spec.ts
  • e2e/golf-qualifier.spec.ts
  • e2e/golf-round.spec.ts
  • e2e/golf-team-join.spec.ts
  • e2e/helpers/auth.ts
  • e2e/helpers/common.ts
  • e2e/messages.spec.ts
  • e2e/player-profile.spec.ts
  • e2e/roster.spec.ts
  • e2e/watchlist.spec.ts
  • scripts/__tests__/verify-baseball-demo-coverage.test.ts
  • scripts/seed-baseball-demo.ts
  • scripts/seed-baseball-surfaces-demo.ts
  • scripts/verify-baseball-demo-coverage.ts
  • src/app/admin/teams/[id]/page.tsx
  • src/app/api/admin/log-event/route.ts
  • src/app/api/cron/integrity-check/route.ts
  • src/app/api/log-error/route.ts
  • src/app/baseball/(dashboard)/dashboard/performance/builder/page.tsx
  • src/app/baseball/(dashboard)/dashboard/performance/live/page.tsx
  • src/app/baseball/(player-dashboard)/player/today/__tests__/page.test.tsx
  • src/app/baseball/(player-dashboard)/player/today/page.tsx
  • src/app/baseball/actions/__tests__/publish-lift-day-helm-bridge.integration.test.ts
  • src/app/baseball/actions/lift-builder.ts
  • src/app/baseball/actions/lifting-v11.ts
  • src/app/baseball/actions/lifting.ts
  • src/app/baseball/actions/program-settings.ts
  • src/app/golf/actions/__tests__/crm-engagement.test.ts
  • src/app/golf/actions/__tests__/round-recap.test.ts
  • src/app/golf/actions/crm-engagement.ts
  • src/app/golf/actions/crm-gmail-send.ts
  • src/app/golf/actions/crm-insights.ts
  • src/app/golf/actions/insights.ts
  • src/app/golf/actions/round-recap.ts
  • src/app/golf/admin/crm/coach/[id]/page.tsx
  • src/app/golf/admin/crm/components/InboundLeadsView.tsx
  • src/lib/__tests__/telemetry-gate.test.ts
  • src/lib/admin-logger.ts
  • src/lib/admin/data/__tests__/team-detail.test.ts
  • src/lib/admin/data/errors.ts
  • src/lib/admin/data/team-detail.ts
  • src/lib/admin/data/triage.ts
  • src/lib/baseball/lifting/group-audit-writer.ts
  • src/lib/baseball/read-models/decision-room/readiness.ts
  • src/lib/baseball/read-models/lift-builder.ts
  • src/lib/baseball/read-models/lift-programs.ts
  • src/lib/baseball/read-models/live-weight-room.ts
  • src/lib/baseball/read-models/performance-command.ts
  • src/lib/baseball/read-models/player-snapshot-cards.ts
  • src/lib/baseball/read-models/player-today.ts
  • src/lib/baseball/read-models/stat-visuals.ts
  • src/lib/baseball/read-models/strength-groups.ts
  • src/lib/coachhelm/baseball/loaders-v10.ts
  • src/lib/server-error-logger.ts
  • src/lib/telemetry-gate.ts
  • src/lib/utils/__tests__/describe-error.test.ts
  • src/test/api/admin-log-event.test.ts
  • src/test/api/log-error.test.ts
  • supabase/migrations/20260704090000_graveyard_legacy_liftlab_tables_phase3.sql
  • supabase/migrations/20260704100000_demo_requests_admin_read_update.sql
  • supabase/migrations/20260704110000_crm_rpcs_admin_gate.sql
  • supabase/migrations/20260704120000_helm_lifting_group_audit.sql
  • supabase/migrations/20260704130000_integrity_check_admin_readability_tripwire.sql
  • supabase/tests/rls/baseball_strength_group_audit.sql
  • supabase/tests/rls/baseball_v11_lifting_visibility.sql

Summary by CodeRabbit

  • New Features

    • Golf and baseball lifting flows now use the newer Helm lifting data paths, improving consistency across dashboards and reports.
    • Team detail views now show a more accurate “last activity” signal.
  • Bug Fixes

    • Login and redirect behavior now works for both coach and player paths.
    • Error and event logging now handles empty or malformed requests more gracefully.
    • Several pages now wait for real UI/network readiness, reducing flaky behavior and screenshot issues.

Walkthrough

This PR migrates baseball lifting/strength data access from legacy baseball_* tables to unified helm_lifting_* tables across read-models, actions, seed scripts, and RLS migrations; adds graveyard/audit/RPC-gating migrations; stabilizes Playwright e2e tests by replacing fixed waits with deterministic conditions; and hardens admin/error logging body parsing, runtime environment tagging, CRM error serialization, and lead status mapping.

Changes

Helm Lifting Lab Migration

Layer / File(s) Summary
Database migrations
supabase/migrations/20260704090000_*, 20260704100000_*, 20260704110000_*, 20260704120000_*, 20260704130000_*, supabase/tests/rls/*, src/app/api/cron/integrity-check/route.ts
Graveyards 16 legacy tables, gates demo_requests/CRM RPCs to admin, adds helm_lifting_group_audit table with RLS, adds run_integrity_checks() tripwire, and updates pgTAP RLS coverage.
Group audit writer
src/lib/baseball/lifting/group-audit-writer.ts
Rewrites GroupAuditEntry/appendGroupAudit to write Helm-shaped audit rows into helm_lifting_group_audit.
Read-model migration
src/lib/baseball/read-models/*, src/lib/coachhelm/baseball/loaders-v10.ts
Repoints readiness, lift-builder, lift-programs, live-weight-room, performance-command, player-snapshot-cards, player-today, stat-visuals, and strength-groups queries to helm_lifting_* tables with athlete/player id bridging.
Server actions & pages
src/app/baseball/actions/*, src/app/baseball/(dashboard)/.../builder/page.tsx, .../live/page.tsx, player/today/page.tsx
Updates saveLiftSessionPlan, createLiftAssignment, updateProgramBlockOrder, and dashboard pages to resolve groups/exercises via helm tables; suppresses logging for expected LiftingForbiddenError.
Publish-lift-day integration test
src/app/baseball/actions/__tests__/publish-lift-day-helm-bridge.integration.test.ts
Rewrites tests/fakes to assert direct helm_lifting_sessions materialization, idempotency, stage-and-swap, and fail-fast behavior.
Seed & verification scripts
scripts/seed-baseball-demo.ts, scripts/seed-baseball-surfaces-demo.ts, scripts/verify-baseball-demo-coverage.ts, scripts/__tests__/*
Seeds helm_lifting_groups/helm_lifting_group_members via athlete resolution, force-resets demo CI user passwords, and updates coverage checks.

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

Playwright E2E Stabilization

Layer / File(s) Summary
CI gate and shared helpers
.github/workflows/playwright.yml, e2e/helpers/auth.ts, e2e/helpers/common.ts
Adds required-secrets check step; updates SUCCESS_URL_RE and waitForPageLoad timeout handling.
Auth spec
e2e/auth.spec.ts
Uses shared SUCCESS_URL_RE for player login redirect assertion.
Discover/messages/watchlist/profile specs
e2e/discover.spec.ts, e2e/messages.spec.ts, e2e/watchlist.spec.ts, e2e/player-profile.spec.ts, e2e/course-library.spec.ts
Replaces waitForTimeout with networkidle waits, URL/state polling, and element-attachment checks.
Golf specs
e2e/golf-dashboard.spec.ts, e2e/golf-qualifier.spec.ts, e2e/golf-round.spec.ts, e2e/golf-team-join.spec.ts
Gates tests on env-seeded credentials, switches to relative routes, and replaces fixed waits with deterministic conditions.

Estimated code review effort: 3 (Moderate) | ~30 minutes

Admin Telemetry Gating and Error Logging Hardening

Layer / File(s) Summary
Runtime env gating
src/lib/telemetry-gate.ts, src/lib/admin-logger.ts, src/lib/server-error-logger.ts, src/lib/__tests__/telemetry-gate.test.ts
Adds RuntimeEnv/getRuntimeEnv(), blocks persistence in CI, and tags persisted metadata with runtimeEnv.
Log routes hardening
src/app/api/admin/log-event/route.ts, src/app/api/log-error/route.ts, src/test/api/*
Parses body as text first, returns 204 for empty bodies, 400 for invalid JSON, adds runtimeEnv to metadata.
Admin data queries
src/lib/admin/data/errors.ts, triage.ts, team-detail.ts, src/app/admin/teams/[id]/page.tsx
Excludes info-severity events from errors/triage feeds; adds fail-soft teamLastActivity to team detail.
CRM error serialization
src/app/golf/actions/crm-engagement.ts, crm-gmail-send.ts, crm-insights.ts, src/lib/utils/__tests__/describe-error.test.ts
Introduces describeError for structured Supabase/PostgREST error logging with graceful degradation.
Round recap & CRM misc
src/app/golf/actions/round-recap.ts, insights.ts, src/app/golf/admin/crm/coach/[id]/page.tsx
Adds opt-in revalidate flag to generateRoundRecap, forces dynamic rendering, switches gated-count log to info event.
Inbound leads status mapping
src/app/golf/admin/crm/components/InboundLeadsView.tsx
Maps converted leads to completed status per CHECK domain and derives status booleans.

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

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant LogRoute as "log-event/log-error route"
  participant TelemetryGate as "telemetry-gate"
  participant AdminEvents as "admin_events table"

  Client->>LogRoute: POST body (text)
  LogRoute->>LogRoute: check empty body
  alt empty body
    LogRoute-->>Client: 204
  else invalid JSON
    LogRoute-->>Client: 400 Invalid JSON
  else valid JSON
    LogRoute->>TelemetryGate: shouldPersistAdminTables()
    TelemetryGate-->>LogRoute: true/false (CI/env check)
    LogRoute->>TelemetryGate: getRuntimeEnv()
    TelemetryGate-->>LogRoute: runtimeEnv tag
    LogRoute->>AdminEvents: insert row with metadata.runtimeEnv
    LogRoute-->>Client: 200 success
  end
Loading
sequenceDiagram
  participant Action as "lift-builder / lifting action"
  participant HelmGroups as "helm_lifting_groups"
  participant HelmMembers as "helm_lifting_group_members"
  participant HelmAthletes as "helm_lifting_athletes"
  participant HelmSessions as "helm_lifting_sessions"

  Action->>HelmGroups: resolve organization_id/team_id for group
  Action->>HelmMembers: fetch athlete_id for group
  Action->>HelmAthletes: map athlete_id -> sport_player_id
  Action->>HelmSessions: materialize/update sessions per athlete
  HelmSessions-->>Action: session rows (legacy_baseball_id null)
Loading

Possibly related PRs

  • njrini99-code/helmv3#573: Both PRs modify the publishLiftDay Helm materialization behavior and its integration test coverage for staging/updating session-exercises and preventing duplicate re-publish.
  • njrini99-code/helmv3#587: Both PRs modify src/lib/baseball/read-models/player-today.ts to rewire Player Today's lift/readiness inputs from legacy baseball_* sources to helm_lifting_* tables.
  • njrini99-code/helmv3#740: Both PRs change the same telemetry/admin incident logging gating code (telemetry-gate.ts, log-error/admin/log-event routes, admin-logger/server-error-logger).

Suggested labels: security, database, ci

✨ 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 feat/liftlab-helm-unification
  • 🛠️ helm safety pass
  • 🛠️ dashboard ux pass
  • 🛠️ rls test pass

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.

🔧 ast-grep (0.44.0)

ast-grep could not parse rule config: /ast-grep-rules/../git/.coderabbit/ast-grep/no-explicit-any.yml


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

@njrini99-code
njrini99-code merged commit ac6e17b into main Jul 3, 2026
30 of 36 checks passed
@njrini99-code
njrini99-code deleted the feat/liftlab-helm-unification branch July 3, 2026 16:35
@coderabbitai coderabbitai Bot added ci CI, Vercel, test, build, deploy database Schema, migrations, indexes, SQL security Auth, secrets, RLS, PII, webhooks labels Jul 3, 2026
@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Lift Lab helm unification, CRM RLS fixes, admin health badge, e2e stabilization

✨ Enhancement 🐞 Bug fix 🧪 Tests ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Lift Lab writers migrated to helm_lifting_*; publishLiftDay now materializes helm sessions
 directly.
• CRM admin fixes: add missing RLS, align demo_requests.status domain, admin-gate CRM RPCs.
• Admin team health badge uses unfiltered last activity; CI/e2e stabilized via seed + workflow
 gates.
Diagram

graph TD
  PB["Lifting actions"] --> HL[("helm_lifting_*")]
  PB -.->|removed writes| LEG[("legacy lift tables")]
  LEG --> GY["graveyard schema"]
  RM["Baseball read-models"] --> HL
  GAW["Group audit writer"] --> HGA[("helm_lifting_group_audit")]
  CRMUI["Inbound Leads UI"] --> DR[("demo_requests")]
  RPC["CRM RPCs"] --> CRMDB[("crm_* / email_events")]
  PW["Playwright CI"] --> SEED["seed-baseball-demo"]
  SEED --> AUTH[("auth users/login_attempts")]
  ADMIN["Admin team detail"] --> GR[("golf_rounds")]

  subgraph Legend
    direction LR
    _db[(Database)] ~~~ _mod["Module/Route"]
  end
Loading
High-Level Assessment

The chosen strategy is the right convergence point: with legacy Lift Lab tables verified empty, moving all writers to helm_lifting_* and then relocating the legacy tables is the cleanest way to enforce “one train, one deploy” and prevent regression. CRM fixes correctly address the root causes (missing admin RLS + invalid status domain + ungated SECURITY DEFINER RPCs) with minimal behavioral change. CI/e2e changes target the underlying failure modes (credential drift, lockouts, missing secrets, flake-prone waits) rather than adding more retries.

Files changed (70) +3561 / -3052

Enhancement (5) +77 / -4
page.tsxEnsure Today page can consume helm-lifting sessions reliably +16/-0

Ensure Today page can consume helm-lifting sessions reliably

• Adds/adjusts logic to support helm-native lifting session reads without legacy bridge assumptions.

src/app/baseball/(player-dashboard)/player/today/page.tsx

round-recap.tsRefine round recap generation +38/-4

Refine round recap generation

• Updates round recap logic and improves testability/consistency of outputs.

src/app/golf/actions/round-recap.ts

page.tsxAdjust coach CRM detail page for new CRM constraints +7/-0

Adjust coach CRM detail page for new CRM constraints

• Minor UI/data loading adjustments consistent with admin gating changes.

src/app/golf/admin/crm/coach/[id]/page.tsx

errors.tsAdd structured error helpers for admin data layer +9/-0

Add structured error helpers for admin data layer

• Introduces/extends shared error helpers used in admin data fetching paths.

src/lib/admin/data/errors.ts

triage.tsAdd triage helpers for admin workflows +7/-0

Add triage helpers for admin workflows

• Introduces small utilities supporting admin triage flows in the data layer.

src/lib/admin/data/triage.ts

Bug fix (18) +428 / -111
auth.tsFix post-login URL contract for player login +13/-2

Fix post-login URL contract for player login

• Uses a regex matching '/baseball/dashboard|/baseball/player' instead of a coach-only '/dashboard' glob to avoid hanging on player logins.

e2e/helpers/auth.ts

common.tsMake waitForPageLoad tolerant of never-idle pages +9/-2

Make waitForPageLoad tolerant of never-idle pages

• Caps the 'networkidle' wait and swallows timeouts to avoid indefinite hangs on pages with background polling.

e2e/helpers/common.ts

seed-baseball-demo.tsForce-reset CI demo passwords + clear lockouts at seed start +52/-12

Force-reset CI demo passwords + clear lockouts at seed start

• Ensures the two CI identities are always reset to the expected password and clears 'login_attempts' before auth creation, preventing credential drift from causing persistent lockouts.

scripts/seed-baseball-demo.ts

verify-baseball-demo-coverage.tsUpdate seed coverage checks for renamed/shifted tables +2/-2

Update seed coverage checks for renamed/shifted tables

• Adjusts coverage verification to match the current demo seed output after schema changes.

scripts/verify-baseball-demo-coverage.ts

page.tsxUse unfiltered teamLastActivity for health badge +1/-5

Use unfiltered teamLastActivity for health badge

• Stops deriving activity from roster-completed rounds; instead uses the new 'teamLastActivity' from the admin data loader to match '/admin/golf' behavior.

src/app/admin/teams/[id]/page.tsx

route.tsHarden admin log-event endpoint against empty sendBeacon bodies +27/-19

Harden admin log-event endpoint against empty sendBeacon bodies

• Reads request body as text first and returns 204 on empty payloads, preventing noise incidents; tags persisted metadata with runtime environment.

src/app/api/admin/log-event/route.ts

route.tsHarden error logging endpoint against malformed/empty bodies +41/-4

Harden error logging endpoint against malformed/empty bodies

• Parses defensively (text → JSON) with 204 on empty body and 400 on invalid JSON; normalizes fields and tags persisted rows with runtimeEnv.

src/app/api/log-error/route.ts

crm-engagement.tsAlign CRM engagement action with admin-gated RPCs +53/-24

Align CRM engagement action with admin-gated RPCs

• Updates CRM engagement logic to work with the new admin gating and related CRM/RLS constraints.

src/app/golf/actions/crm-engagement.ts

crm-gmail-send.tsAdjust Gmail send action to updated CRM gating/contracts +4/-3

Adjust Gmail send action to updated CRM gating/contracts

• Updates the Gmail send action for compatibility with new CRM access constraints.

src/app/golf/actions/crm-gmail-send.ts

crm-insights.tsAlign CRM insights with admin-gated RPCs +22/-6

Align CRM insights with admin-gated RPCs

• Updates CRM insights computations/calls to match new security restrictions.

src/app/golf/actions/crm-insights.ts

insights.tsStabilize insights action behavior +12/-7

Stabilize insights action behavior

• Small adjustments for consistency and better determinism in admin/insights computations.

src/app/golf/actions/insights.ts

InboundLeadsView.tsxMap Inbound Leads statuses to CHECK-allowed demo_requests domain +27/-20

Map Inbound Leads statuses to CHECK-allowed demo_requests domain

• Fixes list filters and Add-to-CRM updates to use 'pending'/'completed' (instead of invalid 'new'/'converted') to prevent silent update failures.

src/app/golf/admin/crm/components/InboundLeadsView.tsx

admin-logger.tsMinor adjustments for improved admin logging behavior +5/-2

Minor adjustments for improved admin logging behavior

• Small tweaks to admin logger behavior consistent with telemetry gating and endpoint robustness changes.

src/lib/admin-logger.ts

team-detail.tsExpose teamLastActivity via direct golf_rounds query +29/-2

Expose teamLastActivity via direct golf_rounds query

• Adds a separate one-row query (no roster/status filters) returning the latest 'golf_rounds.created_at' for the team, matching '/admin/golf' health classification inputs.

src/lib/admin/data/team-detail.ts

server-error-logger.tsMinor error logging adjustments +6/-1

Minor error logging adjustments

• Small changes aligned with new defensive parsing and telemetry gate behavior.

src/lib/server-error-logger.ts

telemetry-gate.tsPrevent admin telemetry persistence in CI and tag runtime env +37/-0

Prevent admin telemetry persistence in CI and tag runtime env

• Updates 'shouldPersistAdminTables()' to explicitly exclude CI/GitHub Actions; adds 'getRuntimeEnv()' for metadata tagging of persisted rows.

src/lib/telemetry-gate.ts

20260704100000_demo_requests_admin_read_update.sqlAdd admin SELECT/UPDATE policies for demo_requests +26/-0

Add admin SELECT/UPDATE policies for demo_requests

• Fixes admin UI visibility by adding admin-gated SELECT and UPDATE RLS policies to 'demo_requests' (previously only insertable publicly).

supabase/migrations/20260704100000_demo_requests_admin_read_update.sql

20260704110000_crm_rpcs_admin_gate.sqlAdmin-gate CRM SECURITY DEFINER functions +62/-0

Admin-gate CRM SECURITY DEFINER functions

• Adds explicit admin checks to 'get_crm_events_in_range' and 'get_crm_time_to_open' to prevent exposure to non-admin authenticated users.

supabase/migrations/20260704110000_crm_rpcs_admin_gate.sql

Refactor (21) +1686 / -2584
golf-team-join.spec.tsRemove dead placeholder golf team join tests +2/-112

Remove dead placeholder golf team join tests

• Deletes long-skipped placeholder tests that used hardcoded localhost URLs and never ran in CI.

e2e/golf-team-join.spec.ts

roster.spec.tsRemove dead placeholder roster spec +0/-207

Remove dead placeholder roster spec

• Deletes an unused placeholder spec file that never executed meaningfully in CI.

e2e/roster.spec.ts

seed-baseball-surfaces-demo.tsAlign surfaces demo seed with helm lifting tables +84/-18

Align surfaces demo seed with helm lifting tables

• Updates the demo seed paths to target helm-lifting schemas/columns consistent with the unification work.

scripts/seed-baseball-surfaces-demo.ts

page.tsxPoint performance builder UI at helm-backed lifting actions +2/-2

Point performance builder UI at helm-backed lifting actions

• Adjusts builder page wiring to align with helm-lifting unification changes.

src/app/baseball/(dashboard)/dashboard/performance/builder/page.tsx

page.tsxAlign live performance UI with helm lifting data flow +16/-11

Align live performance UI with helm lifting data flow

• Updates live performance page behavior to match helm-based session/materialization changes.

src/app/baseball/(dashboard)/dashboard/performance/live/page.tsx

lift-builder.tsUpdate lift-builder action wiring for helm tables +19/-5

Update lift-builder action wiring for helm tables

• Adjusts lift builder server actions to match the helm-lifting schema and identity mapping.

src/app/baseball/actions/lift-builder.ts

lifting-v11.tsHelm-native Lift Lab actions (no legacy writes/bridge) +812/-1107

Helm-native Lift Lab actions (no legacy writes/bridge)

• Large rewrite to exclusively read/write 'helm_lifting_*', including publishLiftDay materialization, identity mapping (player→athlete), and removal of legacy dual-space behavior.

src/app/baseball/actions/lifting-v11.ts

lifting.tsAlign lifting facade with helm-native v11 implementation +16/-19

Align lifting facade with helm-native v11 implementation

• Updates lifting action wrapper/exports to match renamed/adjusted v11 action behavior.

src/app/baseball/actions/lifting.ts

program-settings.tsAdjust program settings to helm-lifting schema +3/-3

Adjust program settings to helm-lifting schema

• Updates program settings action(s) to use helm-lifting identifiers/columns.

src/app/baseball/actions/program-settings.ts

group-audit-writer.tsWrite group audit events to helm_lifting_group_audit +78/-32

Write group audit events to helm_lifting_group_audit

• Migrates the append-only audit writer from legacy 'baseball_strength_group_audit' to the new helm table, adapting to the new column schema and using untyped inserts.

src/lib/baseball/lifting/group-audit-writer.ts

readiness.tsSwap readiness reads to helm lifting tracking tables +41/-46

Swap readiness reads to helm lifting tracking tables

• Updates readiness/bodyweight/availability reads from legacy tables to helm equivalents.

src/lib/baseball/read-models/decision-room/readiness.ts

lift-builder.tsSwap lift builder read model to helm tables +57/-38

Swap lift builder read model to helm tables

• Updates template/section/prescription reads to pull from 'helm_lifting_*' instead of legacy tables.

src/lib/baseball/read-models/lift-builder.ts

lift-programs.tsSwap lift programs read model to helm tables +115/-57

Swap lift programs read model to helm tables

• Updates lift program fetching (weeks/days/sections/prescriptions) to helm-lifting schema and key names.

src/lib/baseball/read-models/lift-programs.ts

live-weight-room.tsSwap Live Weight Room reads to helm sessions/exercises +59/-40

Swap Live Weight Room reads to helm sessions/exercises

• Updates Live Weight Room read paths to helm sessions and removes reliance on legacy dual-space resolution.

src/lib/baseball/read-models/live-weight-room.ts

performance-command.tsSwap performance command reads to helm tables +115/-77

Swap performance command reads to helm tables

• Updates performance command queries to helm assignments/sessions/exercises with athlete identity mapping.

src/lib/baseball/read-models/performance-command.ts

player-snapshot-cards.tsSwap player snapshot reads to helm tables +49/-21

Swap player snapshot reads to helm tables

• Updates snapshot cards read queries to helm sessions/body tracking tables.

src/lib/baseball/read-models/player-snapshot-cards.ts

player-today.tsSwap player today reads to helm sessions +21/-16

Swap player today reads to helm sessions

• Updates today’s lift session queries to 'helm_lifting_sessions'/'helm_lifting_session_exercises'.

src/lib/baseball/read-models/player-today.ts

stat-visuals.tsSwap stat visuals reads to helm maxes/PRs +31/-21

Swap stat visuals reads to helm maxes/PRs

• Updates max/PR queries to 'helm_lifting_maxes' and 'helm_lifting_prs'.

src/lib/baseball/read-models/stat-visuals.ts

strength-groups.tsSwap strength group read model to helm groups/members +61/-115

Swap strength group read model to helm groups/members

• Moves strength group queries off legacy tables and removes dead legacy detail helper paths.

src/lib/baseball/read-models/strength-groups.ts

loaders-v10.tsSwap remaining loader reads from legacy to helm tables +9/-9

Swap remaining loader reads from legacy to helm tables

• Updates residual legacy reads in v10 loader paths to target helm equivalents.

src/lib/coachhelm/baseball/loaders-v10.ts

database.tsPrune generated DB types for removed legacy Lift Lab tables +96/-628

Prune generated DB types for removed legacy Lift Lab tables

• Removes large blocks of legacy 'baseball_lift_*' and related table types after unification and graveyard migration, reducing type drift.

src/lib/types/database.ts

Documentation (1) +9 / -3
route.tsDocument new integrity check for admin list readability +9/-3

Document new integrity check for admin list readability

• Updates route docs to note the new 'admin_count_vs_list_readability' tripwire in the integrity check RPC output.

src/app/api/cron/integrity-check/route.ts

Other (25) +1361 / -350
playwright.ymlFail-fast verification for required CI secrets +23/-0

Fail-fast verification for required CI secrets

• Adds an early step that errors immediately when required baseball E2E secrets are missing, avoiding 15–20 minute delayed failures later in the job.

.github/workflows/playwright.yml

auth.spec.tsStabilize auth flow assertions +10/-4

Stabilize auth flow assertions

• Updates auth spec expectations and URL handling to better match real post-login navigation and reduce flake.

e2e/auth.spec.ts

course-library.spec.tsStabilize course library e2e and gate golf fixtures +24/-6

Stabilize course library e2e and gate golf fixtures

• Refactors assertions away from hard sleeps and uses env-gated fixtures so golf specs can self-skip when credentials are not configured.

e2e/course-library.spec.ts

discover.spec.tsRefactor discover e2e for web-first assertions +20/-14

Refactor discover e2e for web-first assertions

• Replaces brittle waits/sleeps with UI state assertions to reduce timing flake.

e2e/discover.spec.ts

golf-dashboard.spec.tsGate golf dashboard e2e and reduce flake +24/-14

Gate golf dashboard e2e and reduce flake

• Moves the spec to env-gated execution and replaces time-based waits with web-first expectations.

e2e/golf-dashboard.spec.ts

golf-qualifier.spec.tsStabilize qualifier e2e via assertions and fixtures +29/-24

Stabilize qualifier e2e via assertions and fixtures

• Refactors the qualifier flow to rely less on sleeps and more on deterministic page state checks; uses env-gated fixtures.

e2e/golf-qualifier.spec.ts

golf-round.spec.tsStabilize round logging e2e via assertions and fixtures +33/-20

Stabilize round logging e2e via assertions and fixtures

• Reduces flake by replacing sleeps with assertions and gating execution on configured golf credentials.

e2e/golf-round.spec.ts

messages.spec.tsRefactor messages e2e to reduce sleeps +10/-5

Refactor messages e2e to reduce sleeps

• Replaces brittle timing waits with state-based assertions for improved reliability.

e2e/messages.spec.ts

player-profile.spec.tsRefactor player profile e2e to reduce flake +17/-6

Refactor player profile e2e to reduce flake

• Updates assertions and removes unnecessary waits to stabilize profile flow coverage.

e2e/player-profile.spec.ts

watchlist.spec.tsRefactor watchlist e2e to reduce timing flake +17/-8

Refactor watchlist e2e to reduce timing flake

• Moves toward web-first assertions and removes unnecessary fixed delays.

e2e/watchlist.spec.ts

verify-baseball-demo-coverage.test.tsAdjust demo coverage test for updated seed behavior +7/-1

Adjust demo coverage test for updated seed behavior

• Updates the test to reflect the seed script’s new invariants and coverage checks.

scripts/tests/verify-baseball-demo-coverage.test.ts

page.test.tsxUpdate player today tests for helm lifting surfaces +24/-4

Update player today tests for helm lifting surfaces

• Adjusts expectations around Today page lift session reads now that materialization is helm-native.

src/app/baseball/(player-dashboard)/player/today/tests/page.test.tsx

publish-lift-day-helm-bridge.integration.test.tsRewrite publishLiftDay integration tests for direct-helm materialization +76/-63

Rewrite publishLiftDay integration tests for direct-helm materialization

• Removes bridge-specific assumptions and validates idempotent publish behavior and session snapshot correctness against helm tables directly.

src/app/baseball/actions/tests/publish-lift-day-helm-bridge.integration.test.ts

crm-engagement.test.tsAdd CRM engagement tests +187/-0

Add CRM engagement tests

• Introduces a new test suite covering CRM engagement behavior, including admin gating and status-domain expectations.

src/app/golf/actions/tests/crm-engagement.test.ts

round-recap.test.tsStrengthen round recap tests +56/-4

Strengthen round recap tests

• Updates tests to match round recap output after e2e/stability and admin-data changes.

src/app/golf/actions/tests/round-recap.test.ts

telemetry-gate.test.tsAdd coverage for CI blocking + runtime env tagging +62/-1

Add coverage for CI blocking + runtime env tagging

• Adds tests for CI/GitHub Actions exclusion in 'shouldPersistAdminTables()' and for 'getRuntimeEnv()' classification.

src/lib/tests/telemetry-gate.test.ts

team-detail.test.tsUpdate team detail tests for teamLastActivity +94/-2

Update team detail tests for teamLastActivity

• Expands coverage to assert the new unfiltered team activity signal used by the admin health badge.

src/lib/admin/data/tests/team-detail.test.ts

describe-error.test.tsAdd describeError test coverage +68/-0

Add describeError test coverage

• Adds unit tests validating error description formatting for logging/telemetry paths.

src/lib/utils/tests/describe-error.test.ts

admin-log-event.test.tsAdd API tests for /api/admin/log-event robustness +126/-0

Add API tests for /api/admin/log-event robustness

• Introduces tests covering empty body (204), invalid JSON (400), and successful logging behavior.

src/test/api/admin-log-event.test.ts

log-error.test.tsAdd API tests for /api/log-error robustness +46/-0

Add API tests for /api/log-error robustness

• Adds tests ensuring empty/malformed sendBeacon payloads do not create server incidents and return proper status codes.

src/test/api/log-error.test.ts

20260704090000_graveyard_legacy_liftlab_tables_phase3.sqlMove 16 legacy Lift Lab tables into graveyard schema +60/-0

Move 16 legacy Lift Lab tables into graveyard schema

• Adds a post-deploy migration that moves legacy lift/strength/body tracking tables from 'public' to 'graveyard', removing realtime publication membership and preserving FK integrity within graveyard.

supabase/migrations/20260704090000_graveyard_legacy_liftlab_tables_phase3.sql

20260704120000_helm_lifting_group_audit.sqlCreate helm_lifting_group_audit with RLS +44/-0

Create helm_lifting_group_audit with RLS

• Creates the missing helm mirror for strength-group audit logging and adds RLS policies for authenticated users who can edit the org.

supabase/migrations/20260704120000_helm_lifting_group_audit.sql

20260704130000_integrity_check_admin_readability_tripwire.sqlAdd integrity check for admin list readability vs count RPCs +126/-0

Add integrity check for admin list readability vs count RPCs

• Extends 'run_integrity_checks()' with a policy-readability tripwire that verifies admin UI tables have SELECT/ALL policies applying to 'authenticated', preventing badge-vs-list RLS mismatches.

supabase/migrations/20260704130000_integrity_check_admin_readability_tripwire.sql

baseball_strength_group_audit.sqlUpdate RLS tests to target helm group audit table +70/-73

Update RLS tests to target helm group audit table

• Adjusts pgTAP coverage to validate RLS behavior on 'helm_lifting_group_audit' instead of the legacy audit table.

supabase/tests/rls/baseball_strength_group_audit.sql

baseball_v11_lifting_visibility.sqlUpdate lifting visibility RLS tests for helm tables +108/-101

Update lifting visibility RLS tests for helm tables

• Updates visibility assertions to reflect helm-native Lift Lab tables after writer unification.

supabase/tests/rls/baseball_v11_lifting_visibility.sql

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0d6c25d7e1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +269 to +271
const { data: memberRows } = await fromUntyped(supabase, 'helm_lifting_group_members')
.select('athlete_id')
.in('group_id', groups) as { data: Array<{ athlete_id: string }> | null };

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reject groups outside the active baseball team

After switching this path to helm_lifting_group_members, the action trusts any supplied groupScope and uses the returned athlete IDs directly. In an org with multiple baseball teams or cross-sport Lift Lab groups, a stale/forged group id can read another team/sport's group membership under the org-scoped helm RLS and then create sport: 'baseball' sessions under ctx.targetTeamId for those athletes. Validate the referenced helm_lifting_groups rows against organization_id, sport = 'baseball', and team_id = ctx.targetTeamId before materializing sessions.

Useful? React with 👍 / 👎.

Comment on lines +315 to +317
const { data: memberRows } = await fromUntyped(supabase, 'helm_lifting_group_members')
.select('athlete_id')
.eq('group_id', input.groupId) as { data: Array<{ athlete_id: string }> | null };

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Scope builder group plans to the active team

This builder path now resolves members from the cross-sport, org-scoped helm_lifting_group_members table without first proving that input.groupId is a baseball group for ctx.targetTeamId. If a request supplies another team’s group id in the same org, the code maps those athletes back to player ids and later creates sessions with the active team's team_id, assigning plans to players outside the active roster. Validate the group row's org/sport/team, or intersect the resolved players with the current team roster before creating sessions.

Useful? React with 👍 / 👎.

@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (3) 📘 Rule violations (3) 📜 Skill insights (0)

Context used
✅ Compliance rules (platform): 97 rules

Grey Divider


Action required

1. chain typed as any 📘 Rule violation ⚙ Maintainability
Description
The new test helper sets const chain: any = {}, introducing an explicit any type that bypasses
TypeScript safety. This violates the compliance rule that disallows any in changed TypeScript
code.
Code

src/app/golf/actions/tests/crm-engagement.test.ts[42]

+  const chain: any = {};
Relevance

⭐⭐⭐ High

Team previously accepted removing explicit-any/casts once types available; likely will fix new
explicit any in tests.

PR-#296

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 1519320 disallows explicit any usage. The added test file defines `const chain:
any = {} in createAdminChain(), which is a direct any` annotation.

Rule 1519320: Disallow use of the any type in TypeScript
src/app/golf/actions/tests/crm-engagement.test.ts[40-45]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`const chain: any = {}` introduces an explicit `any`, which defeats TypeScript type-checking.

## Issue Context
This test file is newly added and can be typed without using `any` by defining a small interface/type for the mocked chain (including its `then` signature).

## Fix Focus Areas
- src/app/golf/actions/__tests__/crm-engagement.test.ts[40-53]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. admin as any assertion 📘 Rule violation ⚙ Maintainability
Description
getCoachEngagement casts admin to any via (admin as any), which bypasses type checking in a
changed server action. This violates the compliance requirement to avoid any in TypeScript.
Code

src/app/golf/actions/crm-engagement.ts[R86-89]

+    const { data, error } = await (admin as any)
+      .from('crm_coach_engagement')
+      .select('coach_id, score, temperature, opens_90d, clicks_90d, last_event_at')
+      .in('coach_id', coachIds);
Relevance

⭐⭐⭐ High

Team accepted removing (supabase as any) casts in actions; suggests compliance against any
assertions is enforced.

PR-#296

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 1519320 disallows any (including via assertions). The changed code asserts
(admin as any) before making a Supabase query, introducing any in the modified lines.

Rule 1519320: Disallow use of the any type in TypeScript
src/app/golf/actions/crm-engagement.ts[82-89]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The code uses `(admin as any)` which introduces `any` via a type assertion, bypassing type safety.

## Issue Context
This file is a server action module; the query can be typed by giving `createAdminClient()` a concrete return type (or by creating a narrow interface for the subset used: `.from().select().in()`), avoiding `any`.

## Fix Focus Areas
- src/app/golf/actions/crm-engagement.ts[83-90]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. CRM bucket cast crash 🐞 Bug ≡ Correctness
Description
get_crm_time_to_open() can emit the bucket value 86400+ but still casts `split_part(bucket, '-',
1) to int`, which throws for that bucket and causes the RPC to fail at runtime. The CRM insights
server action then logs the error and returns an empty dataset, silently breaking the time-to-open
chart.
Code

supabase/migrations/20260704110000_crm_rpcs_admin_gate.sql[R56-60]

+  bucketed AS (
+    SELECT CASE WHEN seconds_to_open < 60 THEN '0-60' WHEN seconds_to_open < 600 THEN '60-600' WHEN seconds_to_open < 3600 THEN '600-3600' WHEN seconds_to_open < 14400 THEN '3600-14400' WHEN seconds_to_open < 86400 THEN '14400-86400' ELSE '86400+' END AS bucket FROM paired
+  )
+  SELECT (split_part(bucket, '-', 1))::int, CASE WHEN bucket = '86400+' THEN 999999 ELSE (split_part(bucket, '-', 2))::int END, COUNT(*)::int
+  FROM bucketed GROUP BY bucket ORDER BY 1;
Relevance

⭐⭐ Medium

No historical evidence found for SQL 86400+ bucket cast pattern; unclear enforcement.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The migration defines a bucket label '86400+' and immediately casts split_part(bucket, '-', 1)
to int, which will fail for '86400+'. The admin insights action calls this RPC and returns []
on any error, so the failure manifests as missing chart data.

supabase/migrations/20260704110000_crm_rpcs_admin_gate.sql[56-60]
src/app/golf/actions/crm-insights.ts[179-200]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The SQL function `public.get_crm_time_to_open` constructs a bucket label `'86400+'` for opens >= 24h, but still casts `split_part(bucket, '-', 1)` to `int`. For `'86400+'`, `split_part(..., 1)` returns `'86400+'`, which is not castable to integer, causing the RPC to error.

## Issue Context
This RPC is called by the admin CRM insights page to render the time-to-open distribution; when it errors, the server action returns `[]`, silently hiding the chart data.

## Fix Focus Areas
- supabase/migrations/20260704110000_crm_rpcs_admin_gate.sql[56-60]

## Suggested fix
Change the SELECT to guard *both* `bucket_min` and `bucket_max` for the `'86400+'` bucket, e.g.:
- `bucket_min := CASE WHEN bucket='86400+' THEN 86400 ELSE split_part(bucket,'-',1)::int END`
- `bucket_max := CASE WHEN bucket='86400+' THEN 999999 ELSE split_part(bucket,'-',2)::int END`
Alternatively, avoid string buckets entirely by bucketing on numeric ranges and formatting labels in the app layer.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

4. CI gate misses truthy 🐞 Bug ☼ Reliability
Description
shouldPersistAdminTables()/getRuntimeEnv() only treat CI as CI when process.env.CI or
process.env.GITHUB_ACTIONS equals the literal string 'true', so other common truthy values (e.g.
'1') will fall through. In such environments, the telemetry gate may incorrectly allow persisting
admin tables outside the intended production runtime.
Code

src/lib/telemetry-gate.ts[R30-37]

+  // GitHub Actions sets both CI=true and GITHUB_ACTIONS=true on every job
+  // runner — never persist from there, independent of whatever VERCEL_ENV
+  // happens to read (it's normally unset in CI, but this must not depend
+  // on that staying true).
+  if (process.env.CI === 'true' || process.env.GITHUB_ACTIONS === 'true') return false;
+  // A VERCEL_ENV that exists but isn't 'production' (preview, or a local
+  // override) is excluded explicitly rather than falling through.
+  if (process.env.VERCEL_ENV && process.env.VERCEL_ENV !== 'production') return false;
Relevance

⭐⭐ Medium

Telemetry gate just introduced; no prior feedback about supporting CI env values beyond literal
'true'.

PR-#740
PR-#739

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The gate currently uses strict string equality checks for CI detection, and admin-logger.ts uses
this gate to decide whether to write incident rows; missing CI detection defeats the purpose of
suppressing non-prod telemetry.

src/lib/telemetry-gate.ts[27-56]
src/lib/admin-logger.ts[59-91]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The telemetry gate checks `process.env.CI === 'true'` and `process.env.GITHUB_ACTIONS === 'true'`. If a CI environment sets these variables to a non-`'true'` truthy value (commonly `'1'`), the gate won’t recognize CI and may allow persistence.

## Issue Context
This gate controls whether prod incident tables (`admin_events`, `error_logs`, etc.) are written at all.

## Fix Focus Areas
- src/lib/telemetry-gate.ts[27-56]

## Suggested fix
Treat these env vars as truthy if they are set and not explicitly false, e.g.:
- `const isCI = Boolean(process.env.GITHUB_ACTIONS) || (process.env.CI && process.env.CI !== 'false');`
Then reuse `isCI` in both `shouldPersistAdminTables()` and `getRuntimeEnv()` to keep the classification consistent.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

5. helm_lifting_group_audit missing prefix 📘 Rule violation ⚙ Maintainability
Description
The migration creates public.helm_lifting_group_audit, which does not follow the required golf_
or baseball_ table-name prefix convention. This breaks the enforced naming standard for
sport-scoped tables.
Code

supabase/migrations/20260704120000_helm_lifting_group_audit.sql[R10-24]

+CREATE TABLE IF NOT EXISTS public.helm_lifting_group_audit (
+  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
+  organization_id uuid NOT NULL REFERENCES public.organizations(id) ON DELETE CASCADE,
+  sport text NOT NULL DEFAULT 'baseball',
+  team_id uuid,
+  group_id uuid REFERENCES public.helm_lifting_groups(id) ON DELETE CASCADE,
+  action text NOT NULL,
+  actor_id uuid,
+  target_athlete_id uuid REFERENCES public.helm_lifting_athletes(id) ON DELETE SET NULL,
+  before_state jsonb,
+  after_state jsonb,
+  note text,
+  legacy_baseball_id uuid,
+  created_at timestamptz NOT NULL DEFAULT now()
+);
Relevance

⭐ Low

Repo is standardizing on helm_lifting_* tables; prefix convention not enforced against these helm
tables.

PR-#768

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 1519306 requires table names to start with golf_ or baseball_. The added
migration defines CREATE TABLE ... public.helm_lifting_group_audit, which does not match either
prefix.

Rule 1519306: Enforce sport-specific table name prefixes
supabase/migrations/20260704120000_helm_lifting_group_audit.sql[10-24]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A new table is created with the name `helm_lifting_group_audit`, which does not start with `golf_` or `baseball_` as required.

## Issue Context
The compliance rule requires sport-specific table identifiers to use `golf_` or `baseball_` prefixes. The new table should be renamed (and any dependent references updated) to comply.

## Fix Focus Areas
- supabase/migrations/20260704120000_helm_lifting_group_audit.sql[10-24]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. Lift plan ignores errors 🐞 Bug ☼ Reliability
Description
saveLiftSessionPlan() ignores Supabase error results when resolving group members via
helm_lifting_group_members and helm_lifting_athletes, and then nullish-coalesces to [], making
DB/RLS failures indistinguishable from an empty group. The action can then return `{ success: true,
count: 0 }`, masking the underlying failure and preventing the UI from surfacing it.
Code

src/app/baseball/actions/lift-builder.ts[R315-329]

+      const { data: memberRows } = await fromUntyped(supabase, 'helm_lifting_group_members')
+        .select('athlete_id')
+        .eq('group_id', input.groupId) as { data: Array<{ athlete_id: string }> | null };
+      const memberAthleteIds = [...new Set((memberRows ?? []).map((m) => m.athlete_id))];
+
+      if (memberAthleteIds.length > 0) {
+        const { data: athleteRows } = await fromUntyped(supabase, 'helm_lifting_athletes')
+          .select('sport_player_id')
+          .in('id', memberAthleteIds) as { data: Array<{ sport_player_id: string | null }> | null };
+        playerIds = [...new Set(
+          (athleteRows ?? [])
+            .map((a) => a.sport_player_id)
+            .filter((id): id is string => Boolean(id)),
+        )];
+      }
Relevance

⭐ Low

Similar “don’t treat Supabase failure as empty list” suggestions were rejected (data ?? [] kept).

PR-#564
PR-#336

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The code destructures only data for both helm queries and then uses (memberRows ?? []) /
(athleteRows ?? []), which collapses null data into empty arrays. Immediately afterward it returns
success when playerIds is empty, creating a misleading success path on read failure.

src/app/baseball/actions/lift-builder.ts[308-350]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
In `saveLiftSessionPlan`, the new helm group-member resolution path destructures only `data` from Supabase calls and never checks `error`. When these reads fail (RLS denial, transient connectivity, schema mismatch), the code treats it as an empty result set and returns a successful no-op.

## Issue Context
This path is used when `input.groupId` is provided. The function currently returns success with `count: 0` when `playerIds.length === 0`.

## Fix Focus Areas
- src/app/baseball/actions/lift-builder.ts[308-350]

## Suggested fix
Capture `{ data, error }` for both queries and handle `error` explicitly:
- If either query returns `error`, throw a `BaseballActionError` (so the UI can show a failure), or return `{ success: false, error: ... }`.
- Optionally `logServerError` with enough context (teamId, groupId) to debug.
This ensures real backend failures don’t silently appear as “0 athletes in group”.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo


function createAdminChain() {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const chain: any = {};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

1. chain typed as any 📘 Rule violation ⚙ Maintainability

The new test helper sets const chain: any = {}, introducing an explicit any type that bypasses
TypeScript safety. This violates the compliance rule that disallows any in changed TypeScript
code.
Agent Prompt
## Issue description
`const chain: any = {}` introduces an explicit `any`, which defeats TypeScript type-checking.

## Issue Context
This test file is newly added and can be typed without using `any` by defining a small interface/type for the mocked chain (including its `then` signature).

## Fix Focus Areas
- src/app/golf/actions/__tests__/crm-engagement.test.ts[40-53]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +86 to +89
const { data, error } = await (admin as any)
.from('crm_coach_engagement')
.select('coach_id, score, temperature, opens_90d, clicks_90d, last_event_at')
.in('coach_id', coachIds);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

2. admin as any assertion 📘 Rule violation ⚙ Maintainability

getCoachEngagement casts admin to any via (admin as any), which bypasses type checking in a
changed server action. This violates the compliance requirement to avoid any in TypeScript.
Agent Prompt
## Issue description
The code uses `(admin as any)` which introduces `any` via a type assertion, bypassing type safety.

## Issue Context
This file is a server action module; the query can be typed by giving `createAdminClient()` a concrete return type (or by creating a narrow interface for the subset used: `.from().select().in()`), avoiding `any`.

## Fix Focus Areas
- src/app/golf/actions/crm-engagement.ts[83-90]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +56 to +60
bucketed AS (
SELECT CASE WHEN seconds_to_open < 60 THEN '0-60' WHEN seconds_to_open < 600 THEN '60-600' WHEN seconds_to_open < 3600 THEN '600-3600' WHEN seconds_to_open < 14400 THEN '3600-14400' WHEN seconds_to_open < 86400 THEN '14400-86400' ELSE '86400+' END AS bucket FROM paired
)
SELECT (split_part(bucket, '-', 1))::int, CASE WHEN bucket = '86400+' THEN 999999 ELSE (split_part(bucket, '-', 2))::int END, COUNT(*)::int
FROM bucketed GROUP BY bucket ORDER BY 1;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

4. Crm bucket cast crash 🐞 Bug ≡ Correctness

get_crm_time_to_open() can emit the bucket value 86400+ but still casts `split_part(bucket, '-',
1) to int`, which throws for that bucket and causes the RPC to fail at runtime. The CRM insights
server action then logs the error and returns an empty dataset, silently breaking the time-to-open
chart.
Agent Prompt
## Issue description
The SQL function `public.get_crm_time_to_open` constructs a bucket label `'86400+'` for opens >= 24h, but still casts `split_part(bucket, '-', 1)` to `int`. For `'86400+'`, `split_part(..., 1)` returns `'86400+'`, which is not castable to integer, causing the RPC to error.

## Issue Context
This RPC is called by the admin CRM insights page to render the time-to-open distribution; when it errors, the server action returns `[]`, silently hiding the chart data.

## Fix Focus Areas
- supabase/migrations/20260704110000_crm_rpcs_admin_gate.sql[56-60]

## Suggested fix
Change the SELECT to guard *both* `bucket_min` and `bucket_max` for the `'86400+'` bucket, e.g.:
- `bucket_min := CASE WHEN bucket='86400+' THEN 86400 ELSE split_part(bucket,'-',1)::int END`
- `bucket_max := CASE WHEN bucket='86400+' THEN 999999 ELSE split_part(bucket,'-',2)::int END`
Alternatively, avoid string buckets entirely by bucketing on numeric ranges and formatting labels in the app layer.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread src/lib/telemetry-gate.ts
Comment on lines +30 to +37
// GitHub Actions sets both CI=true and GITHUB_ACTIONS=true on every job
// runner — never persist from there, independent of whatever VERCEL_ENV
// happens to read (it's normally unset in CI, but this must not depend
// on that staying true).
if (process.env.CI === 'true' || process.env.GITHUB_ACTIONS === 'true') return false;
// A VERCEL_ENV that exists but isn't 'production' (preview, or a local
// override) is excluded explicitly rather than falling through.
if (process.env.VERCEL_ENV && process.env.VERCEL_ENV !== 'production') return false;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

5. Ci gate misses truthy 🐞 Bug ☼ Reliability

shouldPersistAdminTables()/getRuntimeEnv() only treat CI as CI when process.env.CI or
process.env.GITHUB_ACTIONS equals the literal string 'true', so other common truthy values (e.g.
'1') will fall through. In such environments, the telemetry gate may incorrectly allow persisting
admin tables outside the intended production runtime.
Agent Prompt
## Issue description
The telemetry gate checks `process.env.CI === 'true'` and `process.env.GITHUB_ACTIONS === 'true'`. If a CI environment sets these variables to a non-`'true'` truthy value (commonly `'1'`), the gate won’t recognize CI and may allow persistence.

## Issue Context
This gate controls whether prod incident tables (`admin_events`, `error_logs`, etc.) are written at all.

## Fix Focus Areas
- src/lib/telemetry-gate.ts[27-56]

## Suggested fix
Treat these env vars as truthy if they are set and not explicitly false, e.g.:
- `const isCI = Boolean(process.env.GITHUB_ACTIONS) || (process.env.CI && process.env.CI !== 'false');`
Then reuse `isCI` in both `shouldPersistAdminTables()` and `getRuntimeEnv()` to keep the classification consistent.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci CI, Vercel, test, build, deploy database Schema, migrations, indexes, SQL security Auth, secrets, RLS, PII, webhooks

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant