Whole-App Semantic-Block Adversarial Audit — Orchestration Plan (2026-07-11)
Status: Archived
Owner directive: flag remaining work → decompose the app into semantic blocks (UI/UX + backend) → adversarial audit per block → wireframes + design specs for UI blocks needing fixes → canonical wiring plans for backend blocks → final implementation plan. Fully autonomous run.
Remaining work from the alert-model run (flagged)
- BLOCKED (owner action):
npx supabase db pushfor migration00385_alert_model_wiring.sql— denied by the permission classifier (prod DB deploy). Locally validated twice (doubledb reset+ synthetic trigger probes). Owner: run the push, or add a Bash permission rule. Until it lands, prod emits no new signals and the severity audit / DM rename aren't live (client copes fine — no regression). - Docs sync: screen-blueprint Notifications section + signal-system spec → real ledger (92 types after wiring; realtime-retired note). DELEGATED.
- Device QA of the 3-band notification center (owner, next preview open).
- Minor documented gaps from 00385: (a) 00384's inline hold-cancel path (session completion) doesn't emit
session_payment_hold_expired; (b) a payer-cancelled hold doesn't auto-resolve itshold_createdsignal. - Phase D of the alert plan (preferences/digest UI) — deferred by design.
- PIPA privacy text — OWNER-owned, do not edit.
Block decomposition
UI/UX blocks (audit → wireframe/design-spec for anything broken)
| ID | Block | Scope anchors |
|---|---|---|
| U1 | Home + notification center | home-feed-screen, temporal panes, admin strip, bell, notification-center (fresh — verify only) |
| U2 | Clubs | directory, MyClubCard v3, club-detail tabs (info/members/schedule/board/admin), settings |
| U3 | Sessions | create wizard, session-detail two-surface IA, RSVP/waitlist flow, roster, 정산 screen |
| U4 | Matches | match board, MatchScoreboard, score entry, live rounds, swap/editor, score-call consensus UI |
| U5 | 기록/Records | analytics, leaderboards, ELO/tier display, profile stats, TMI |
| U6 | Discovery | 번개 찾기, court/venue directory, filters, venue detail (trust ring/confidence) |
| U7 | Auth/Profile/Settings + DM | onboarding, login, profile edit, settings, consents, DM threads |
| U8 | Dues | 회비 현황, generation flow, payment tracking, member-side dues |
Backend blocks (audit → canonical wiring plan for anything broken)
| ID | Block | Scope anchors |
|---|---|---|
| B1 | Clubs domain | RLS, membership, roles, join/guest/invite flows, archive/transfer |
| B2 | Sessions/RSVP | lifecycle, waitlist, auto-advance, forfeit/substitute, holidays/recurrence absence |
| B3 | Payments | settle-before-play holds, dual-rail, PortOne scaffold, refunds |
| B4 | Matches + rating | generator, score consensus, corrections, apply_match_ratings, ELO/tier |
| B5 | Attendance/trust + dues | strikes, excused, dues engine, trust tiers (Phase 6 spec vs reality) |
| B6 | Venues + weather | provenance pipeline, resolver, directory gates, weather crons |
| B7 | Platform plumbing | edge fns + crons inventory, signals residuals, telemetry, storage |
| B8 | Auth/identity | auth flows, consents, PIPA surfaces, account lifecycle |
Protocol
- Agents run in batches of ≤3 (Sonnet). Each agent WRITES its full findings to
docs/audits/blocks/<ID>-<slug>.mdand returns only a ≤15-line summary (context economy). Findings format: What works (evidence) / What's broken or drifted (evidence, severity) / Adversarial probes run / Canon violations. Evidence = file:line + runtime/DB probes, not prose. - After all 16: synthesis. UI fixes → consolidated wireframe artifact(s) + design-spec sections appended to block docs. Backend fixes → wiring-plan docs per area. Then
docs/architecture/implementation-plan-2026-07-audit.mdwith sliced, ordered, effort-tagged work. - Batches: [docs-sync + U1 + U2] → [U3 U4 U5] → [U6 U7 U8] → [B1 B2 B3] → [B4 B5 B6] → [B7 B8 + synthesis start].
Status ledger (update as batches land)
- [x] docs-sync (a138f01d..5fa9c9b0: blueprint 3-band, spec 92 types + realtime-retired, rebuild-log)
- [x] U1 (3 broken: CRIT push-tap dead-end 12/13 categories; HIGH 3-way admin count inconsistency; MED signalMatchesScreen gaps)
- [x] U2 (11 findings: CRIT join-request approval UI missing entirely — hooks built but unconsumed; CRIT dues partial-payment amount discarded; HIGH no tap-to-profile on member rows; ICU localeCompare landmines x2)
- [x] U4 (12 findings: CRIT disputed-match escalation is dead code — score_status='disputed' never written by any migration; CRIT team-slot swap has no server-side status gate — RLS/trigger both miss it; HIGH referee-transfer fully orphaned (5 hooks, 0 consumers); HIGH reject-score/correction buttons skip useConfirm + misuse destructive-button convention)
- [x] U5 (13 findings: HIGH TierInfoSheet raw-RN-View canon violation; HIGH EloTrendCard sparkline plots reversed-chronology with inverted trend color; HIGH region-leaderboard 1-player state shows zero rows; MED-HIGH challenge achievement bridge only wired on 기록 screen; MED localeCompare x4 on matchId tiebreak; MED leaderboards cap top-50 with no my-rank fallback; MED podium not tap-to-profile; MED zero-match new-user wall of empty fragments; LOW tie-break-free ELO ordering, avatar-less partner rows, dead gender/cross-rating tier code, signal misroute to 프로필, DEFAULT_ELO literal drift)
- [x] U6 (8 findings: HIGH new-user guest-apply funnel leak — onboarding-intent.ts has no path rule for /sessions/[id]/preview, so a signup from a pickup card lands on Home post-onboarding; HIGH 미확인 포함 toggle surfaces unverified venues with zero card-level visual distinction from corroborated ones; MED club-discovery sort reintroduces the banned localeCompare/ICU pattern (club-card-model.ts) the sibling venue-directory screen explicitly documents avoiding; MED directory empty state can't distinguish "no courts" from "filtered out by trust gate"; MED 3 diverged hand-rolled region/district filter encodings (pickup/venue/club discovery); LOW naver_place business_hours/review_keywords dormant (scrape-only, no product consumer); LOW dead routes.login push (AuthGate ignores it); INFO verified the WebView venue-correction picker is SAFE in the current pinned OTA binary — a previously-flagged native-module risk is resolved/stale). No code modified.
- [x] U8 (12 findings: CRITICAL waive writes no audit trail + notifies nobody + never resolves the member's outstanding overdue signals — a permanently-stuck critical alert; CRITICAL partial-payment transitions equally invisible to signals (extends U2's discarded-amount finding); HIGH no bank-account/payment-info surface anywhere — member dead-ends at "contact 총무"; HIGH ex-members' unpaid dues are immortal (no active-membership filter in cron or admin-snapshot RPC); MEDIUM-HIGH mid-month join has no dues-generation path once the CTA's empty-list gate closes; MEDIUM signal tap loses year/month context (always opens current month); MEDIUM currency-format drift (formatWon vs t().common.won, same screen); MEDIUM no bulk mark-paid; MEDIUM dead i18n
signalssubtree in settings.ts shadowed by signals.ts with factually-wrong duplicate copy; LOW-MEDIUM second orphanedduesi18n subtree describing unshipped duplicate-warning/reminder features; LOW 0-dues clubs still show full 회비 UI; LOW DuesPartialPaymentModal reinvents parseWon. Also confirmed CRITICAL cross-surface bug: 4 mutually-inconsistent "overdue" definitions across get_home_signals/get_club_admin_snapshot/signals_tick_dues/SummaryCard — AdminTab strip can show "0 overdue" while the cron already pushed a critical severely-overdue alert for the same row). No code modified. - [x] U7 (16 findings: CRIT OAuth unreachable from UI — LoginScreen email-only, Google/Apple adapters are throw-stubs, Kakao built but buttonless + EXT-11 credential-blocked; CRIT signal_preferences per-category prefs have ZERO settings UI — dead
/settings/signal-preferencesroute + unused quiet-hours i18n confirmed, Phase D deferral accurate; HIGH generic 푸시 알림 toggle silently grants/revokes marketing_push PIPA consent (unlabeled, conflicts with consent-management screen); HIGH OAuth cancel/failure silently swallowed — cancel bare-returns, failure sets ctx.error nobody reads (email path re-throws, OAuth never did); HIGH 6/9 user_preferences notify booleans have no toggle anywhere; MED DM inbox 15s-poll doc/code drift (canon + 2 in-code comments claim a poll useDmThreads deliberately omits); MED login-screen raw inline-styled TextInputs (canon); MED empty-DM-inbox CTA gap (copy says "message someone", header + only creates groups); MED deleted-partner thread header falls back to '메시지' instead of existing unknownUser; MED change-password current-password field cosmetic per its own comment. VERIFIED CLEAN: push soft-prompt strictly post-first-RSVP (canon claim holds), user blocking fully built end-to-end, dm_push_preview_enabled fully wired, zero Realtime channels in DM, registry/i18n/ModalPanel/Pressable all compliant). No code modified. - [ ] U3
- [x] B1 (8 findings: CRITICAL owner account-deletion permanently locks a solo-owner club — delete_account_atomic/anonymize_withdrawn_profiles never touch club_members, and transfer_club_ownership requires the caller to still hold owner, so no RPC path recovers it; CRITICAL ghost members (any deleted account) are never deactivated in club_members — permanent capacity-slot leak; HIGH guest_applications never got the 00329-style RPC-only lockdown club_join_request got — raw admin PATCH can "approve" without creating the RSVP, and neither decide_guest_application nor the auto-approve trigger checks session capacity/status; MEDIUM role_configs (custom manage_dues permission) has zero server-side backing — dormant only because no write UI exists yet; MEDIUM no signal on member-left/removed, club settings changes, or unarchive; LOW invites table fully dead code; LOW clubs/club_members/invites/guest_applications predate the explicit-GRANT convention; INFO confirmed U2's "dead join-request pipeline" is UI-only — the server side (00213/00320/00321/00328/00329) is fully built and adversarially hardened, zero new migrations needed for that fix). No code modified.
- [x] B2 (8 findings: CRITICAL
PublicPickupPreviewScreen— built for non-member/unauthenticated visitors — is backed by an RLS-gatedsessions_select(is_club_member-only, never extended forpublic_invite_enabled) that can never return data for that exact audience, so every non-member preview renders "session not found"; CRITICAL/HIGH voluntary pre-session RSVP cancellation never promotes the waitlist — the only general-purpose promotion code (manageRsvpUseCase.handleCancel) is dead (zero callers; productionuseCancelRsvpbypasses it) and would fail RLS if wired up, leaving only 2 narrow live promotion paths (fee-hold-expiry cron, mid-session forfeit substitute); HIGH capacity gate (assign_waitlist_position, 00312) has an un-acknowledged TOCTOU race — no row/advisory lock, so concurrent last-seat RSVPs can both pass; HIGH status machine is a 2-rule blocklist not an allowlist —cancelled→completedand state-skips are server-unblocked, only client-UI-gated; MEDIUM no trigger blocks RSVP-confirm on a locked/cancelled/completed session; MEDIUMmax_playersdecrease doesn't reconcile existing over-quota confirmed seats or re-lock; LOWsignals_on_rsvp_updateSECDEF function still hassearch_path=public(missed both hardening passes); INFOrsvps_deleteRLS policy is dead code. Also verified clean: UNIQUE(session_id,user_id) dupe-proofing, RSVP-forging blocked by RLS, 00384 completion-trigger double-fire safety, 00313 hold-expiry TOCTOU fix, 00319/00322's self-documented residual gaps (independently rediscovered, nothing new there), forfeit/auto-substitute per-round double-book safety, cascade-skip idiom applied consistently. No code modified.) - [x] B3 (10 findings: CRITICAL settle-before-play match gate
enforce_settled_match_participantsis dead code on the swap path —UPDATE OF participant_idsnever fires forupdateMatchTeams's team-slot-only SET list, so an unsettled participant can be swapped into a fee-session match with zero server rejection — empirically reproduced on live local DB; CRITICAL (pre-ship)00385_alert_model_wiring.sqlsilently reverts00313's attest-vs-expiry TOCTOU lock/re-check fix inexpire_unpaid_session_holds— confirmed live via\sf, not yet pushed to prod so still cheaply fixable in place; MEDIUMsession_paymentsUPDATE RLS (00145 admin + 00303 host) has noWITH CHECK, so an authorized host/admin could tamper withamount/user_idvia a raw update; MEDIUM no fee-change-after-holds-exist reconciliation (latent, no shipped UI reaches it); MEDIUM confirmed both previously-flagged signal gaps real (00384 inline completion-cancel emits nohold_expiredsignal; payer-cancel doesn't resolvehold_created, self-heals ≤10min); LOW noamount>=0CHECK; LOW legacycost_totalcreateBatch can collide with an auto-hold via UNIQUE constraint (fails loud, UI already gates it); INFO PortOne edge-fn scaffold (verify + webhook) is well-built, no doc drift; SECDEF sweep clean —mark_session_payment_paid_via_portonecorrectly service_role-only. Refund lifecycle (00307), authority model, double-pay, self-pay, 0-fee-hold, waitlist-double-hold, paid-no-show all confirmed CLEAN. Full findings + fix priority:docs/audits/blocks/B3-payments-backend.md. No code modified.) - [x] B4 (14 findings: CRITICAL
submit_match_scorehas no status guard — an admin/match_director can silently rewrite a COMPLETED match's score with zero consent, bypassing 00308's all-agree correction flow entirely, whileapply_match_ratings' idempotency guard silently skips re-rating, permanently desyncing the displayed score fromelo_history/player_matchups(new, not filed by U4/B3); CRITICALscore_statusindependently re-confirmed never written to'disputed'by any migration (cross-ref U4 #1, backend fix shape owned here — patchreject_call); CRITICAL fix-surface synthesis — TWO independent unprotected paths into a finalized match need closing together: the RLS status-predicate gap (U4 #2/B3 #1) AND thesubmit_match_scoreRPC gap (new, bypasses RLS via SECURITY DEFINER); MEDIUM RSVP tier gate uses cross-poolelo_ratingnot a format-aware singles/doubles rating; MEDIUM no server-side membership/RSVP check on match participant slots at insert or swap time; LOW-MEDIUM no round-level player-uniqueness invariant (client-only guarantee); LOW-MEDIUM legacyverify_match_scoreRPC +verifyScoreadapter method fully orphaned (0 consumers), semantics now conflict with the consensus path'sverified_bywrite; LOW 2 signal triggers usesearch_path=publicnot''; LOW referee-transfer proposal has no status gate (moot, feature unreachable per U4 #3). VERIFIED CLOSED (answers the task brief's explicit question): the tiebreak-ELO-winner bug is FIXED by 00323 and the fix is verbatim-preserved through 00385's current live definition, NOT regressed (contrast B3's finding of an actual regression elsewhere in the same migration). Also confirmed clean: concurrent admin-submit vs consensus-agree finalization race is safe (Postgres row-locking),match_score_callsRLS/consensus surface fully closed (agree-for-others impossible, auto-call dedup race-free), and a 00209 blanket SECDEF sweep closed what looked like 9+ individual missing-REVOKE gaps across the match RPC surface. Full findings + fix priority:docs/audits/blocks/B4-matches-rating-backend.md. No code modified.) - [x] B6 (12 findings: CRITICAL
ingest-venuesedge fn's token gate is fail-OPEN —if (token && ...)skips auth entirely whenINGEST_TOKENis unset, exposing real venue writes AND a destructive?action=resetmass-delete of the whole imported-venue corpus, in stark contrast to the siblingenrich-venuesfn and all 4 weather crons which correctly fail-closed; CRITICALvenue_correctionsRLS/GRANT was never tightened past its 00339 origin — a raw client INSERT (own-uid only, no other constraint) bypassessubmit_venue_correction's entire validation stack (rate limit, bounds, reputation trust gate), and a malformed value can hard-abortresolve_venue_factsfor an ENTIRE batch (empirically up to 400 venues per the realemit-observations.mjspipeline, unbounded on the--sqlmigration-generation path) since neither that function norenrich_venues_batchhas per-row exception isolation; HIGH owner/host facts written asvenue_source_observations(provider='manual')(00382's own actual write path) are NOT protected by the "human corrections win" override — that step only reads the separatevenue_correctionsledger, so amanualrow is one ordinary vote and can be silently outvoted by 2 agreeing lower-trust scraped sources on the next resolve pass, contradicting 00382's own doc claim thatmanualis "top-trust"; HIGH a wrongly-closed venue (closed_atset via 2-agreement, trivially gameable via the RLS bypass above) has zero reopen/dispute path anywhere in the codebase — permanent short of manual SQL; MEDIUM tennis-fee parser (00348) fixing "soccer prices on a tennis court" is called only from its one-time backfill migration, never the live ingest path — regresses on any future re-ingest; MEDIUMadm1_id/adm2_idlinkage has no ingest-time guard, confirmed script-only (backfill-venue-adm.mjs), rooting U6's independently-flagged directory empty-state finding; MEDIUM weather-warning escalation (주의보→경보) whileis_activestays true never re-notifies (dedup/early-return keyed only on activation transition); MEDIUM naver_place_id UNIQUE collision (real live incident, 00378/00380) patched at its one diagnosed cause only, not structurally — same missing-exception-isolation class as the CRITICAL resolver finding; LOWfetch-weatherfailure logs reference nonexistentgrid.name(always undefined, breaks cron-failure triage); LOW shared-secret comparisons aren't constant-time. VERIFIED CLEAN: SECDEF hygiene airtight across all 6 resolver/correction redefinitions; 00154 closed an early weather_cache open-write RLS hole; all 4 weather crons correctly fail-closed; 00316's weather-warning session_status enum fix confirmed live and complete (previously-dead safety-notification path is fixed); 영동/영서 + 경기북/남부 region-splitting and the AQI D+6 dual-fetch merge both correctly implemented; 00356/00357's correction-RPC hardening (coord-wipe, pin-walk, reputation trust gate) verified still intact; the just-added 600m Naver branch-mismatch coord guard is sound. Also confirmed (INFO):enrich_venues_batchstill silently drops unnamed JSONB keys, by design. Full findings:docs/audits/blocks/B6-venues-weather-backend.md. No code modified.) - [x] B5 (14 findings, DB-verified against a freshly-reset local Postgres: CRITICAL
mark_session_payment_paid_via_portoneis anon-executable with zero internal auth check — directly financially exploitable via the public anon key, no login required; corrects B3's "verified clean" claim above (static migration-text read vs. live ACL diverged), root cause traced to a systemic post-00209default-ACL gap affecting 41 SECDEF functions app-wide; CRITICAL attendance strike-ladder notifications (attendance_warning_cooldown/attendance_flagged_admin) resurrect indefinitely — every attendance write re-fires them via a stale "last strike session" andsignals_emit's dedup-upsert resetsacknowledged_at/resolved_atto NULL, with zero resolve path anywhere and no decay policy; CRITICAL dueswaived/partialsignal blind spot server-side-confirmed (extends U8 #1/#2); CRITICAL a 5th divergent "overdue" definition found (deadgenerate_dues_reminders()cron writing to a zero-consumerclub_alertstable) — canonicaldues_day-precise definition + unification wiring plan proposed; HIGH systemic anon-EXECUTE leak (ALTER DEFAULT PRIVILEGES … FROM PUBLICdoesn't touch anon's separate default-ACL grant); HIGHattendance_records/dues/manner_tagshave zero explicit data-API GRANT (100% reliant on the forbidden Supabase auto-grant, safe today only because RLS happens to be correctly scoped); MEDIUM-HIGH phantom attendance still open (self-documented in00318, confirmed still live —total_sessions_confirmedbumps unconditionally regardless ofattendance_tracking_enabled); MEDIUMdues_daynot snapshotted per period (retroactive reinterpretation on mid-cycle edit); MEDIUM 20 SECDEF functions app-wide onsearch_path=publicnot''(broader than B2's single-instance flag onsignals_on_rsvp_update); MEDIUM Phase 6 spec-vs-reality gaps (총무 endorsement/manual-demote unimplemented, no dashboard reason surface); MEDIUM 4 declared SignalTypes —trust_tier_changed/manner_tag_received/late_cancel_penalty_applied/no_show_reported— have zero emitters anywhere; LOW-MEDIUMmanner_tagsmissing AFTER-DELETE trust-tier recalc trigger; LOW no server guard on RSVP-cancel-during-live-match (client-gated only, unreachable via shipped app). Also verified CLEAN: strike upsert integrity has no double-counting vector (UNIQUE + overwrite semantics, not increment), excused-state preservation consistent across all 7 writers, guest-sub/forfeit path correctly handled end-to-end,total_sessions_confirmedincrement race already fixed (00144), manner-tag abuse surface well-designed (RLS + CHECK + DISTINCT-count caps collusion), session-completion idempotency guard correct. Full findings:docs/audits/blocks/B5-attendance-trust-dues-backend.md. No code modified.) - [x] B7 (12 findings, live-DB-verified against a freshly-reset local Postgres, HEAD=00386: root cause of the CRITICAL LEAD found — Supabase's platform bootstrap grants EXECUTE to
anonvia its own explicit per-role default-ACL row (pg_default_acl), completely separate fromPUBLIC;REVOKE ... FROM PUBLIC(used in 96 migrations, incl. 00209's own future-proofingALTER DEFAULT PRIVILEGESclause) is a no-op against that row — onlyREVOKE ... FROM PUBLIC, anon(13 migrations use it) actually works, explaining why 40/139 (29%) public SECDEF functions are anon-executable today despite repeated apparent lockdown attempts; CRITICAL full ranked blast-radius list produced — 5 functions (ingest_venues_batch/enrich_venues_batch/apply_venue_coords/record_venue_observations/resolve_venue_facts) have zero internal auth guard and are directly anon-exploitable, escalating B6'singest-venuesfail-open finding: even a fixed edge-function token check would not close this, since the RPC is a second unguarded front door bypassing the edge function entirely; CRITICAL new storage finding —avatars insert own/update ownRLS policies have zero ownership check (qual is justbucket_id='avatars'), and combined with the client's guessableavatars/${userId}-${epoch}path + open bucket listing, any signed-in user can enumerate and deface any other user's avatar; HIGHapp_config(holds the plaintextservice_role_key+cron_secretused by every cron job) has full anon+authenticated table grants, inert today only because RLS has zero policies (deny-all) — one future permissive policy away from a full credential leak; HIGH explicit data-API GRANT convention is 7% compliant schema-wide (6/88 tables), extending B1/B5's individually-flagged tables to the full count, currently non-exploitable only because RLS is universally enabled+policied elsewhere; re-confirmed B6'singest-venuesfail-open edge-fn bug live and confirmed it has NO siblings among the other 6 secret-gated edge functions (all correctly fail-closed); MEDIUM-HIGHseed-scenario's permissive-by-default Bearer accept depends entirely on a correctly-setENVsecret with no enforced safe default; MEDIUMcheck_rate_limit_for_keyanon-callable with attacker-controlled key/thresholds — targeted rate-limit DoS or self-bypass vector; HIGH preventive gap — zero pgTAP/CI regression guard exists anywhere for SECDEF ACL hygiene, so the 96-instance broken idiom will keep re-opening functions with every future migration; full 27-job pg_cron inventory audited clean (cadences sane, no orphans beyond the already-known-dead half of job 25/B5 #13); confirmed deadclub_alertsbridge from the client side (hooks explicitly removed "Round 14 — zero UI consumers", cross-ref B5 #13); telemetry PII spot-checked clean (not exhaustive). Full findings:docs/audits/blocks/B7-platform-plumbing.md. No code modified.) - [x] B8 (10 findings, DB-verified against a freshly-reset local Postgres (HEAD 00386) via live adversarial probes as a simulated unrelated authenticated user: CRITICAL
profiles_selectRLS isUSING (true)— zero row/column-level protection;profile_visibility(00212) is enforced ONLY inside theget_public_profile_viewRPC used by exactly one adapter method, whilefindByIds/findByClub/getPlayerStats/findTopByRating/searchByDisplayName/leaderboard reads all issue raw table selects that bypass it entirely — empirically any authenticated user can read every other user's phone, kakaoid, push_token, birth_year, and all 3 PIPA consent timestamps via a direct PostgREST call, falsifying the adapter's own inline comment claiming RLS enforcement; CRITICALprofiles_updateself-row write has no column allowlist beyond 7 fields infield_protection_triggers(00067) —trust_tier,singles_elo/doubles_elo/singles_rd/doubles_rd,rating_confidence,rating_source,kakao_id,nationality_code,total_sessions_confirmed,global_attendance_rateare all self-writable via raw PATCH, empirically reproduced (self-promoted owntrust_tierto'veteran'andsingles_elo/doubles_eloto 3000, both persisted; control probe on the protectedelo_ratingcorrectly reverted); CRITICALdelete_account_atomic/anonymize_withdrawn_profilesnever touchrsvps/dues/session_payments/elo_history/manner_tags/attendance_records/member_reports/DM/signals— extends B1'sclub_membersghost-row finding into the session domain, and migration 00322's own header comment names this exact gap ("account deletion … is handled separately") but no migration ever implemented the separate handling, so a withdrawn user's future confirmed RSVPs never release and can hold a session artificially at capacity forever; HIGH the U7-flagged marketing-consent conflation has zero current server-side consequence (zero(광고)-prefixed sends anywhere,send-pushnever readsuser_consent) but the §50(8) 2-year re-confirm cron is already running against a ledger that may already contain grants users never knowingly made — dormant landmine, live violation the moment marketing sends ship; MEDIUM the PIPA §30 access-audit RPCrecord_pi_accessis fully dead code (zero call sites outside generated types) — onlypi_access_logwrites ever made are the one baked into account deletion; MEDIUMcan_view_profile_fieldtakes viewer as a caller-supplied argument instead of resolvingauth.uid()internally, letting any authenticated user probe two _other users' club/friendship relationship as a boolean; LOW-MEDIUMnationality_codehas no write-protection trigger (unlikebirth_year's correctly-immutable pattern), inert today but unguarded against the promised verified-IdP path; LOW systemic anon-EXECUTE leak (B5's root cause) reproduces on 2private-schema functions here but is unreachable via PostgREST'spublic/graphql_public-only exposed-schema config — confirms B5's finding, no new exposure; LOWuser_consent/pi_access_log/profilespredate the explicit-GRANT convention (same class as B1 #7), inert today. Also verified CLEAN: consent-ledger append-only integrity is real DB enforcement (no UPDATE/DELETE policy + partial unique index), backdating blocked at the trigger level, under-14 + birth-year immutability correctly enforced, push-token stale-cleanup correctly wired (inline + cron),delete_account_atomic's own scope is internally atomic and correct, SECDEF hygiene clean across all 12 functions checked (allsearch_path='', no drift found in this block). Full findings:docs/audits/blocks/B8-auth-identity-backend.md. No code modified.) - [ ] Wireframe artifact(s)
- [ ] Backend wiring plans
- [ ] Implementation plan