Skip to content

Audit/onboarding supabase security#27

Merged
BigAkins merged 6 commits into
mainfrom
audit/onboarding-supabase-security
Jun 11, 2026
Merged

Audit/onboarding supabase security#27
BigAkins merged 6 commits into
mainfrom
audit/onboarding-supabase-security

Conversation

@BigAkins

@BigAkins BigAkins commented Jun 11, 2026

Copy link
Copy Markdown
Owner

This PR audits and hardens the AkFit onboarding save flow, focusing on Supabase session validation, profile/goal writes, RLS compatibility, and clearer user-facing error handling.

Includes:

  • onboarding final save reliability review
  • Supabase auth/session/JWT validation checks
  • profile and goal write path audit
  • RLS policy verification
  • improved error/debug handling where needed
  • documentation updates for onboarding/Supabase troubleshooting

Greptile Summary

This PR audits and hardens the AkFit onboarding/profile save flow following a production incident where users choosing "Lean Bulk" were blocked from completing onboarding (SQLSTATE 23514, goals_goal_type_check rejecting lean_bulk). The fix ships a corrected migration, centralizes error classification, improves user-facing messages, and adds Sentry observability across all save surfaces.

  • Root-cause fix: New migration 20260611054748_fix_goals_goal_type_check.sql drops and re-adds the goals_goal_type_check constraint with the correct value set (fat_loss, maintenance, lean_bulk), and a pgTAP test + Swift contract test guard against future drift.
  • Error classification: SaveErrorClassification is extracted from OnboardingView and shared with EditGoalView, EditProfileView, and SearchView — replacing divergent inline logic with a single tested rule set that correctly distinguishes nonRetryable (check/RLS/FK violations) from retryable errors, preventing "try again" messages for errors that can never succeed on retry.
  • AuthManager hardening: Cancels the cold-start loading timeout on the first auth event (prevents AuthView flash mid-fetch), and adds a second guestStore.clearAll() guard after fetchUserData to handle the race where "Continue as Guest" is tapped while a session fetch is in progress.

Confidence Score: 5/5

This PR is safe to merge — the database migration is idempotent and transaction-scoped, the Swift changes are well-tested refactors, and the auth race fix is correct.

All changed paths are either pure refactors (centralising existing logic) or hardening additions (Sentry capture, better user messages). The migration follows the correct DROP-then-ADD pattern inside a single transaction, and the pgTAP test plus Swift contract test guard against recurrence of the root-cause bug. No new state machines, no schema changes beyond the targeted constraint fix, and no auth logic is loosened.

No files require special attention.

Important Files Changed

Filename Overview
AkFit/Services/Supabase/SaveErrorClassification.swift New shared classification utility; cleanly separates sessionExpired / nonRetryable / retryable buckets, well-tested, no issues.
AkFit/AkFit/Auth/AuthManager.swift Adds cold-start timeout cancellation and a post-fetch guest-mode re-check; race condition fix is correct and well-commented.
AkFit/AkFitTests/SaveErrorClassificationTests.swift Good coverage of SaveErrorClassification paths; paceRawValues test only checks explicitly listed cases (Pace is not CaseIterable), so an unregistered new case would not be caught.
supabase/migrations/20260611054748_fix_goals_goal_type_check.sql DROP + re-ADD constraint pattern is correct; defensive UPDATE normalizes any legacy muscle_gain rows.
supabase/tests/database/goals_constraint_shape.test.sql pgTAP regression guard for the constraint fix; uses matches/doesnt_match correctly; rolls back cleanly.
AkFit/Services/SentryMonitoring.swift captureNonFatal is correctly ungated from #if DEBUG and guarded by AppConfig.sentryDSN; privacy rule in doc comment is clear.
AkFit/AkFit/Views/Onboarding/OnboardingView.swift Replaces divergent inline classification/messaging with SaveErrorClassification calls; adds Sentry capture; clean refactor.
AkFit/AkFit/Views/Search/SearchView.swift Adds showQuickLogError alert for previously-silent quick-log failures; isQuickLogging guard ensures only one can error at a time.
AkFit/AkFit/Views/Settings/EditGoalView.swift Replaces inline if/else error classification with SaveErrorClassification; adds Sentry capture.
AkFit/AkFit/Views/Settings/EditProfileView.swift Same pattern as EditGoalView — consistent error handling via SaveErrorClassification and Sentry.

Comments Outside Diff (1)

  1. AkFit/AkFitTests/SaveErrorClassificationTests.swift, line 413-420 (link)

    P2 SaveErrorClassification.kind(of:) has an explicit branch that returns .sessionExpired when an AuthError.api carries errorCode == .invalidJWT || errorCode == .badJWT. This path is reachable in production but has no test coverage — a future refactor could silently reclassify these as .retryable.

    Fix in Claude Code Fix in Codex Fix in Cursor

Fix All in Claude Code Fix All in Codex Fix All in Cursor

Reviews (3): Last reviewed commit: "Fix save error review issues" | Re-trigger Greptile

BigAkins and others added 5 commits June 11, 2026 01:01
The live goals table predated migration tracking and enforced
('fat_loss','maintenance','muscle_gain') while the app has always sent
'lean_bulk' — so every Lean Bulk onboarding save and goal edit failed
with SQLSTATE 23514, deterministically, on every retry. 10 of 54
production users were stuck with a profile but no goal; none recovered.

The corrected constraint existed in 20260401000014 but its CREATE TABLE
IF NOT EXISTS silently no-opped against the pre-existing live table.
This migration drops and re-adds the constraint explicitly (applied to
production 2026-06-11; file version matches the recorded remote
migration) and adds a pgTAP shape test so CI fails if the constraint
ever drifts from the app's GoalType raw values again.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Extracts the onboarding error classification into a shared, unit-tested
SaveErrorClassification used by the results step, EditGoalView, and
EditProfileView. Deterministic server rejects (23502/23503/23514/42501/
PGRST204) now show a "server problem — update the app or contact
support" message instead of an infinite "Please try again" loop, which
is what Lean Bulk users saw for two months during the goal_type
constraint incident.

Adds SentryMonitoring.captureNonFatal and reports the critical catch
sites (onboarding save, goal/profile edits, delete-account, user-data
fetch) with code-only tags — Sentry showed zero events while 10 users
were failing onboarding repeatedly. Tags carry enum/code values only,
never PII; offline URLErrors are excluded from fetch capture to avoid
noise.

Includes a GoalType/Pace raw-value contract test mirroring the DB
constraint as an app-side drift tripwire.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
handle(.initialSession/.signedIn) cleared guest mode before the
unbounded fetchUserData await and never re-checked it, while the 10s
cold-start timeout could surface an interactive AuthView mid-
resolution. A user tapping "Continue as Guest" in that window landed
authenticated with guestStore still active — every subsequent log
silently wrote to UserDefaults instead of Supabase.

Re-check guestStore.isActive after the await and cancel the timeout as
soon as an auth event is being processed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Both quick-log catch blocks were intentionally empty, so a failed
insert (flaky network, expired session) dropped the food log with zero
feedback — users believed the entry was logged and daily totals were
silently wrong. Failures now show a "Couldn't log food" alert
(mirroring DashboardView's showDeleteError pattern) and report to
Sentry with code-only tags.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- supabase/README.md: new live-vs-migration drift verification section
  (migration list --linked, db diff, CHECK/FK constraint dumps) — the
  missing procedure that let the goal_type drift ship; adds the three
  untracked migrations rows, water_entries schema, and a warning on
  hand-applying SQL in the dashboard
- docs/onboarding-save-flow.md: two-step save sequence, partial-failure
  matrix, error classification, observability, and the 2026-06 incident
- docs/release-checklist.md: pre-flight drift check, version bump,
  smoke-test list (incl. lean-bulk onboarding), post-release monitoring
- docs/debugging-supabase-errors.md: error-code → cause → next-step map
  for 23514/42501/PGRST*/401/403
- README.md: status section updated from "planning phase" to the
  shipped reality; setup steps now document the real Secrets.xcconfig
  template flow and the intentional fatalError on missing config

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 11, 2026 14:53

@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: 2dd899fb20

ℹ️ 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 +43 to +45
update public.goals
set goal_type = 'lean_bulk'
where goal_type = 'muscle_gain';

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 Drop the old check before rewriting legacy rows

In environments that actually contain legacy goal_type = 'muscle_gain' rows, this UPDATE runs while the old goals_goal_type_check is still in force, so changing those rows to lean_bulk violates the existing check and aborts the migration before the constraint is dropped. The comment says this is meant to be defensive/idempotent for environments with such rows; drop the old constraint first, then normalize the data before adding the new check.

Useful? React with 👍 / 👎.

Copilot AI 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.

Pull request overview

This PR hardens and documents the onboarding “final save” path and related Supabase interactions by (1) fixing/guarding the goals.goal_type contract that caused deterministic onboarding failures, and (2) centralizing Supabase error classification + adding non-fatal telemetry (Sentry) across key save surfaces.

Changes:

  • Adds a Supabase migration + pgTAP regression test to enforce the app↔DB goal_type allowed-value contract (lean_bulk fix).
  • Introduces shared SaveErrorClassification (with unit tests) and wires Sentry non-fatal capture into onboarding, edit flows, quick-log, and fetch/delete error paths.
  • Expands operational docs: schema drift checks, onboarding save behavior, release checklist, and Supabase error-code triage.

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
supabase/tests/database/goals_constraint_shape.test.sql New pgTAP test to assert goals_goal_type_check shape/values (incident regression guard).
supabase/README.md Adds warnings about dashboard-applied SQL, drift-check procedure, and documents water_entries + new tests.
supabase/migrations/20260611054748_fix_goals_goal_type_check.sql Migration to repair production goals_goal_type_check to accept lean_bulk.
README.md Updates local setup instructions (secrets first) and refreshes project status/operational doc pointers.
docs/release-checklist.md Adds operational release checklist including Supabase drift checks and smoke tests.
docs/onboarding-save-flow.md Documents onboarding save sequence, partial-failure behavior, classification, and observability.
docs/debugging-supabase-errors.md Adds a code→cause→next-step map for Supabase/PostgREST/Auth error codes.
AkFit/Services/Supabase/SaveErrorClassification.swift New shared save-error classification + user messaging + log-field helpers.
AkFit/Services/SentryMonitoring.swift Adds captureNonFatal helper for handled errors with strict non-PII tagging.
AkFit/AkFitTests/SaveErrorClassificationTests.swift New unit tests for classification + app↔DB GoalType contract tripwire.
AkFit/AkFit/Views/Settings/EditProfileView.swift Routes save failures through classification + Sentry non-fatal capture.
AkFit/AkFit/Views/Settings/EditGoalView.swift Routes save failures through classification + Sentry non-fatal capture.
AkFit/AkFit/Views/Search/SearchView.swift Adds user-visible alert for quick-log failures + Sentry capture.
AkFit/AkFit/Views/Onboarding/OnboardingView.swift Centralizes error classification and adds Sentry non-fatal telemetry for onboarding save failures.
AkFit/AkFit/Auth/AuthManager.swift Prevents loading-timeout flash during auth resolution; adds Sentry capture for fetch/delete failures and tightens guest/auth transition safety.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +43 to +52
update public.goals
set goal_type = 'lean_bulk'
where goal_type = 'muscle_gain';

alter table public.goals
drop constraint if exists goals_goal_type_check;

alter table public.goals
add constraint goals_goal_type_check
check (goal_type in ('fat_loss', 'maintenance', 'lean_bulk'));
Comment on lines +17 to +21
nonisolated enum SaveErrorClassification {

// MARK: - Outcome buckets

nonisolated enum Kind {
"postgrest_code": SaveErrorClassification.postgrestCode(of: error),
]
)
showQuickLogError = true
"postgrest_code": SaveErrorClassification.postgrestCode(of: error),
]
)
showQuickLogError = true
Comment thread AkFit/Services/Supabase/SaveErrorClassification.swift
Comment thread supabase/migrations/20260611054748_fix_goals_goal_type_check.sql Outdated
@supabase

supabase Bot commented Jun 11, 2026

Copy link
Copy Markdown

Updates to Preview Branch (audit/onboarding-supabase-security) ↗︎

Deployments Status Updated
Database Thu, 11 Jun 2026 15:14:05 UTC
Services Thu, 11 Jun 2026 15:14:05 UTC
APIs Thu, 11 Jun 2026 15:14:05 UTC

Tasks are run on every commit but only new migration files are pushed.
Close and reopen this PR if you want to apply changes from existing seed or migration files.

Tasks Status Updated
Configurations Thu, 11 Jun 2026 15:14:10 UTC
Migrations Thu, 11 Jun 2026 15:14:10 UTC
Seeding Thu, 11 Jun 2026 15:14:13 UTC
Edge Functions Thu, 11 Jun 2026 15:14:14 UTC

View logs for this Workflow Run ↗︎.
Learn more about Supabase for Git ↗︎.

@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: cfbff9163d

ℹ️ 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".

tags: [String: String] = [:]
) {
guard !AppConfig.sentryDSN.isEmpty else { return }
SentrySDK.capture(error: error) { scope in

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 Scrub Supabase errors before sending to Sentry

When this helper is called from the new onboarding/edit save catch paths with a PostgrestError from a constraint or validation failure, capture(error:) sends the raw error object rather than only the sanitized tags. Postgres/PostgREST error details can include failing row values such as profile fields, user IDs, or macro targets, so this new production telemetry can leak user data despite the privacy rule above; capture a scrubbed error/message built from the classification and error code instead.

Useful? React with 👍 / 👎.

@BigAkins BigAkins merged commit 2454ae9 into main Jun 11, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants