Skip to content

Harden lifecycle flows and local verification#110

Merged
Taleef7 merged 3 commits intomainfrom
codex/stabilization-audit
Apr 8, 2026
Merged

Harden lifecycle flows and local verification#110
Taleef7 merged 3 commits intomainfrom
codex/stabilization-audit

Conversation

@Taleef7
Copy link
Copy Markdown
Owner

@Taleef7 Taleef7 commented Apr 8, 2026

Summary

  • Harden lifecycle RLS and add transactional package checkout RPC.
  • Move checkout/payment resubmission flows behind validated server actions and guard future tutor session completion.
  • Align landing pricing/lead form, migrate Next 16 proxy, and add local Supabase verification helpers/tests.

Test Plan

  • npx supabase db reset
  • npm run typecheck
  • npm run lint (0 errors, 5 existing warnings)
  • npm test
  • node scripts/with-local-supabase-env.mjs npx vitest run supabase/__tests__/lifecycle-rls.integration.test.ts
  • npm run build:local
  • npm run test:e2e:local
  • npm audit --audit-level=high (passes; 1 moderate brace-expansion advisory remains)

Copilot AI review requested due to automatic review settings April 8, 2026 01:18
@Taleef7 Taleef7 merged commit cfc72b9 into main Apr 8, 2026
1 check passed
@Taleef7 Taleef7 deleted the codex/stabilization-audit branch April 8, 2026 01:25
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

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 key Supabase-backed lifecycle flows (request/package/payment/session) by tightening RLS, moving checkout into a transactional RPC, and adding local verification helpers/tests; it also updates Next tooling (proxy convention + dependency bumps) and aligns a few UI flows with the new server-side guards.

Changes:

  • Add stricter RLS for initial request/package/payment states, plus a checkout_package RPC and a future-session completion guard in tutor_update_session.
  • Move checkout + payment (re)submission behind validated server actions; add local Supabase env helper + integration/unit tests.
  • Update landing pricing/lead capture UI and migrate to the Next “proxy” convention; bump Next/Supabase CLI/Vitest.

Reviewed changes

Copilot reviewed 20 out of 22 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
vitest.config.ts Expands Vitest excludes to avoid picking up local/tool output directories.
supabase/migrations/20260408000001_harden_lifecycle_rls_and_checkout.sql Adds hardened RLS policies, transactional checkout RPC, unique index guard, and session future-completion guard.
supabase/tests/lifecycle-rls.integration.test.ts Adds local Supabase integration coverage for hardened RLS + checkout RPC behavior.
supabase/tests/lifecycle-hardening.test.ts Adds migration “contract” assertions to ensure hardening primitives stay present.
scripts/with-local-supabase-env.mjs Adds helper to run commands with env sourced from supabase status -o env.
proxy.ts Migrates from middleware export to the new proxy convention.
package.json Adds *:local scripts and bumps Next/eslint-config-next/supabase CLI/vitest.
package-lock.json Locks updated dependency graph for the bumped tooling versions.
lib/supabase/database.types.ts Updates generated types to include new RPC signatures.
lib/services/sessions.ts Reworks session usage adjustment logic and adds tutor-side “future session” guard before RPC call.
lib/services/session-status-transitions.ts Introduces shared status transition helpers for usage adjustment + completion eligibility.
lib/services/tests/session-status-transitions.test.ts Adds unit tests for the new status transition helpers.
eslint.config.mjs Ignores additional generated/tool state directories.
components/dashboards/SessionCompleteForm.tsx Adds disabled messaging support for session completion UI.
app/tutor/sessions/page.tsx Passes disabled reason for upcoming sessions to prevent early completion in UI.
app/page.tsx Aligns landing pricing cards with shared pricing config and adds a lead form section.
app/dashboard/packages/new/page.tsx Moves package checkout to a server action calling the checkout RPC.
app/dashboard/packages/actions.ts Adds server actions for checkout + pending/rejected payment updates using validated ownership checks.
app/dashboard/packages/[id]/page.tsx Routes pending payment updates through server actions and cleans up uploaded files on failure.
app/api/cron/expire-packages/route.ts Fails closed when CRON_SECRET is missing.
app/api/cron/expire-packages/route.test.ts Adds tests for missing/valid cron secret behavior.
.gitignore Ignores additional local/tool state directories and Playwright outputs.

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

if not found then
raise exception 'request not available for checkout';
end if;

Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

checkout_package checks for an existing pending/active package before taking the FOR UPDATE lock on the request row. Under concurrent calls (e.g., double-submit/retry), both transactions can observe no package, then one inserts; the other will still attempt the insert and hit the partial unique index (packages_one_open_package_per_request) with a constraint violation instead of behaving idempotently. Consider moving/repeating the open-package lookup after the request row lock, or catching the unique-violation and returning the existing package id.

Suggested change
select p.id, p.tier_sessions
into v_package_id, v_existing_tier
from public.packages p
where p.request_id = p_request_id
and p.status in ('pending', 'active')
order by p.created_at desc, p.id desc
limit 1;
if v_package_id is not null then
select case v_existing_tier
when 8 then 8000
when 12 then 11000
when 20 then 16000
end into v_amount;
insert into public.payments (
package_id,
payer_user_id,
amount_pkr,
method,
status
)
select
v_package_id,
v_user_id,
v_amount,
'bank_transfer',
'pending'
where not exists (
select 1 from public.payments pay where pay.package_id = v_package_id
);
return v_package_id;
end if;

Copilot uses AI. Check for mistakes.
Comment on lines +131 to +137
if v_package_id is not null then
select case v_existing_tier
when 8 then 8000
when 12 then 11000
when 20 then 16000
end into v_amount;

Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

When an open package already exists for the request, the function returns that package even if the caller passed a different p_tier_sessions, and it silently recalculates the amount from the existing tier. This can lead to a UI/user mismatch (user selects 12 sessions but is routed to an 8-session package). Consider explicitly rejecting tier mismatches (raise an exception) or documenting/encoding the intended behavior (e.g., require callers to pass the existing tier).

Copilot uses AI. Check for mistakes.
Comment on lines +138 to +153
insert into public.payments (
package_id,
payer_user_id,
amount_pkr,
method,
status
)
select
v_package_id,
v_user_id,
v_amount,
'bank_transfer',
'pending'
where not exists (
select 1 from public.payments pay where pay.package_id = v_package_id
);
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

The conditional payment insert for an existing package uses where not exists (select 1 from public.payments ... ) without a uniqueness constraint or lock. Two concurrent checkout_package calls can both pass the not exists check and insert duplicate payment rows for the same package_id, while the application code often assumes a single payment per package (e.g., .maybeSingle()/.single() by package_id). Consider enforcing uniqueness at the DB level (e.g., unique constraint on payments(package_id) or a partial unique index for the “current” payment), or otherwise making the RPC insert concurrency-safe.

Copilot uses AI. Check for mistakes.
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