Commit Graph
7 Commits
Author SHA1 Message Date
Moheeb Zara 5d153b4d68 In-app notification inbox, consolidate /profile into /me, theme realign
In-app notification center (Phase 5, migration 051):
- notifications.read_at + partial unread index. /me Notifications is now
  an inbox: unread styling, click to expand the message + mark read,
  "mark all as read", unread count. markNotificationsRead is a service-
  client write scoped to the caller's own member_id. Always-on channel:
  email-muted ('skipped') notifications still appear in-app.

Consolidate /profile into /me:
- Delete app/(app)/profile/*; /me is the single member page. Port the
  conflict-of-interest privileged-role nudge + last-disclosed date into
  /me's profile editor. Remove the "My profile" sidebar + command-palette
  entries; rename the /me nav label to "My membership". Repoint the
  recruitment empty-state link to /me.

Theme realign:
- Revert the rounded-lg cards introduced in the prior /me visual pass to
  rounded (the app norm, matching the former /profile). Route /me section
  headers through the SectionTitle primitive.

Regression guard:
- integration/auth-embed.test.ts runs the auth layout's
  space_members.select('*, spaces(*)') embed through PostgREST and fails
  on PGRST201, so a future junction table cannot silently break auth again.
2026-05-19 22:51:44 -07:00
Moheeb Zara f2534323da security: enforce https on dues_payment_methods.url at the data layer
The url renders to members as a clickable href. https was enforced only
in the admin Zod action, but RLS lets an admin/board write the row
directly via PostgREST, bypassing Zod, with no DB constraint -- a
malicious admin could store a javascript: URL and XSS members who click.

- Migration 050: CHECK (url ~* '^https://') so the DB is the source of
  truth; integration-tested that the service path cannot store
  javascript:/http:.
- Member-read action filters with isSafeDuesUrl (defense-in-depth).
2026-05-19 21:35:49 -07:00
Moheeb Zara 5ce6b29d4a test: integration coverage for dues_payment_methods RLS and dispatcher skip
- dues_payment_methods RLS: member read, admin-only write, cross-space
  denial, 046 unverified-admin status gate (4 cases).
- notification dispatcher: a muted category becomes 'skipped' while
  billing always sends; no pref row sends (default on). First real
  exercise of the skip path (cron is gated off in prod).
- Fix integration/db.ts rowsAsUser: the SELECT must be the last psql -c
  statement or its output is swallowed (it was never used before).
- Dues card: hide "No active dues subscription" for spaces without
  Stripe, where it read as confusing.
2026-05-19 20:44:08 -07:00
Moheeb Zara e954b08232 fix(rls): status-gate user_has_role_in_space + user_has_permission (046)
Lifts the deliberately-deferred SECURITY DEFINER status-gate. The
deferral's blocking reason ('no way to verify it breaks nothing') is
resolved by the integration harness, so the gap is now closed at the
RLS layer too (defense-in-depth beyond D2's app layer): an unverified/
inactive member can no longer exercise a role or permission via direct
PostgREST. Scoped to the two privilege entrypoints; get_user_space_ids
+ user_effective_roles deliberately untouched so unverified self-reads/
onboarding still work. Migration 046 + schema.sql mirror. New
integration test (5) proves both halves: gap closed AND
current/late/approval/unverified-reads all unbroken. 31 integration
tests green; default pnpm test 532/26 hermetic; build clean.
2026-05-19 14:04:31 -07:00
Moheeb Zara b56ef4e47c test(integration): Stripe webhook route handler end-to-end (F4)
Covers the biggest untested orchestration (where the Phase-3 P0s + the
deeper-audit D5 bug lived): real signature verify (good/bad/missing),
per-space vault secret+config resolution, stripe_webhook_events replay
idempotency, applySubscription -> member_billing upsert +
space_members status, and the D5 out-of-order monotonic guard proven
end-to-end (a stale event does NOT rewind current_period_end). Invokes
the actual POST handler against local Supabase with Stripe-signed
events (customer.subscription.updated -> no network, no payments enum).
26 integration tests green; default pnpm test 532/26 hermetic; build
clean. Non-runtime.
2026-05-19 13:26:00 -07:00
Moheeb Zara 42f6509ba8 test(integration): billing + notification idempotency invariants (F3)
Extends the harness to the money/notification replay invariants
(previously only asserted by app code / manual audit):
- stripe_webhook_events PK -> a retried Stripe event id is rejected
  (replay protection).
- member_billing UNIQUE(space_id,member_id) -> the webhook's
  onConflict upsert never duplicates a member's billing row.
- notifications UNIQUE(space_id,dedupe_key) + ON CONFLICT DO NOTHING
  -> a replayed enqueue collapses to exactly one outbox row.
21 integration tests green on real Postgres; default pnpm test
unaffected (hermetic); build clean. Non-runtime.
2026-05-19 13:19:35 -07:00
Moheeb Zara 6403220c92 test(integration): DB-backed harness validating shipped SQL (045/042/044/043)
Root-cause fix for 'correctness depended on post-deploy manual audits'.
Separate psql-driven suite (pnpm test:integration, own vitest config,
integration/ outside __tests__ so the hermetic pnpm test gate is
untouched; self-skips without a DB). 18 tests run against real Postgres:
- 045 class_signup_tx/class_cancel_tx: RETURNS TABLE->array shape the
  actions destructure, capacity/waitlist/dedupe, and the per-session
  advisory lock under TWO concurrent connections (D11 proven, never
  over-enrolls).
- 042 equipment exclusion constraint (overlap 23P01; cancelled/adjacent
  ok). 044 self-change trigger (role + payment cols blocked; service
  bypass). 043 members_update WITH CHECK (cross-space move blocked).
Disproves the pass-52 flagged risk (signup rpc-shape). Non-runtime;
default pnpm test still 532/26 hermetic; build clean.
2026-05-19 13:01:16 -07:00