Autonomous Wave-2 Run Ledger (2026-07-12, owner asleep)
Status: Archived
Owner: "Continue till completion of all the planned slices. I gotta sleep, run autonomously." Standing DB-push permission granted ([[feedback_standing_db_push_permission]]).
Per-slice protocol (hold rigor — this is correctness-critical + money code): ground (read PS design + verify live vuln) → delegate impl to Sonnet (fresh context) → INDEPENDENTLY verify the fix's reproduction probe → yarn check + supabase test db green → commit → supabase db push to prod → OTA if client. Migration numbers assigned sequentially from HEAD (00402 is highest live).
HARD HOLD (do NOT ship autonomously)
- profiles slice 3 (
profiles_select→ self-only): the slice-2 masking-RPC client OTA (group ddd74020) shipped 2026-07-12; un-updated clients still raw-select profiles. Narrowing now breaks their rosters/leaderboards. Waits for the OTA to bake across the user base — a dated follow-up, not an overnight slice. Keep held.
Queue (dependency order)
- [x] 00403 — PS4-5 participant/RSVP validation + M3 round-uniqueness — SHIPPED to prod, verified
- [x] 2B (00404) — session_payments ledger protection + 2 signal gaps + fee-edit lock — SHIPPED to prod, verified
- [x] 2C (00405) — canonical overdue view + payment_history ledger + strike-resurrection + ex-member excl — committed; OTA+prod-push in progress
- [x] 2D (00406) — SS1-6 sessions/RSVP integrity — verified, shipping
- [x] 2E (00407) — account-deletion completeness + succession + ghost deactivation — verified, shipping
- [ ] Wave-1 tail — check_rate_limit_for_key hardening; venue resolver exception-isolation [backend]
- [ ] 82-table GRANT sweep [backend, generated] — do near-last (highest blast radius)
- [ ] Client batch (single OTA): U4 reject-confirm gate; 2B.3 stale-hold invalidation; Wave-3 dead-pipeline UI (join-request approval, push-tap routing, admin-count SSoT, signal-preferences screen) — as context allows
Log (append each slice)
- 00402 (pre-run) SHIPPED to prod — match+settlement integrity, verified.
- 00403 SHIPPED to prod — match-slot participant validation + round uniqueness, independently verified (structural + 00402-preservation + agent 9 probes).
- 00404 SHIPPED to prod — payments ledger protection + hold-lifecycle signal gaps, verified (amount-tamper reverts).
- 00405 committed (canonical dues overdue + ledger + strikes) — verified (overdue-agreement, overpay-reject, rollup); client-OTA-first (dues_waived enum) then prod migration.
- 00405 SHIPPED to prod (dues canonical overdue + ledger + strikes) — client OTA (group 198f078e) led, then migration; NOT-VALID fix for prod partial-row grandfathering (local seed didn't reproduce; caught on push).
- 00406 SHIPPED to prod — sessions/RSVP integrity (public-preview RLS, waitlist promotion primitive, capacity lock, status allowlist, RSVP-terminal guard, max_players reconcile), verified.
- 00406 SHIPPED to prod — sessions/RSVP integrity (SS1-6), verified.
- 00407 SHIPPED to prod — account-deletion completeness (ban + session-domain cleanup + guest-RSVP release + membership deactivation + sole-owner succession), verified.
- 00408 SHIPPED to prod — check_rate_limit_for_key server-namespace (cross-user DoS) + resolve_venue_facts/enrich_venues_batch per-row exception isolation (batch-abort DoS), verified.
- 00409 SHIPPED to prod — 79-table explicit GRANT sweep (authenticated CRUD, anon removed) w/ 5 anon-policy exceptions kept + 2 prior narrowings preserved; caught doc no-op-vs-authenticated + club_media anon contract via pgTAP. ALL WAVE-1/WAVE-2 BACKEND REMEDIATION COMPLETE.
Phase 2 — Wave-3 pipelines + owner-feedback surfaces (2026-07-12, owner engaged)
Owner reviewed the 4 wireframe surfaces + rescoped ①. Standing DB-push permission still applies. Wireframe live (2nd pass): claude.ai artifact 97e2f841. English-in-artifact OK (app stays Korean).
In flight (3 parallel agents):
- Mechanical client batch (af8f4b9d): PS8 push-tap routing convergence, admin-count SSoT, signalMatchesScreen map, PS2 2B.3 stale-hold invalidation, U4 reject-confirm, PS10 records bugs (sparkline/DEFAULT_ELO/solo-region/tap-profile), ICU localeCompare→plainStringCompare. → verify yarn check + send-push payload (redeploy?) + BUNDLE 3.8 client enum(club_unarchived)+i18n so enum LEADS 00410 → commit → OTA.
- 00410 lifecycle signals (af177347): 3 emitters (member-left / dues_amount-change / unarchive). Migration written, validating. Enum types exist client-side EXCEPT club_unarchived (bundle into batch OTA). PROD-PUSH HELD until enum OTA bakes (overnight window).
- Join-pipeline research (ae03941d): map guest→member funnel + deliberation primitives → design brief.
Owner feedback → backend slices (queued behind 00410, share local DB):
- ③ dues:
sync_dues_paid_amount(00405) updates paid_amount but NEVER status → completing final installment collides w/dues_partial_requires_balanceCHECK. FIX: auto-derive status (paid≥amount→'paid', 0<x<amount→'partial', never override 'waived'). Owner's completion rule, server-enforced. Dues are ADMIN-recorded (payment_history_insert = owner/admin RLS) — ③ UI is 총무-side, not member self-pay (open Q: member self-report alt). - ④ bank: NEW
club_bank_accountstable (club_id UNIQUE FK, bank_name/account_number/account_holder, admin-write, MEMBER-ONLY SELECT RLS) — account numbers must NOT sit on discovery-readable clubs row (PII isolation). No existing bank storage (payment_method vocab 00285 is unrelated). Copy=icon top-right, no amount prefill. - ② signal-prefs: backend ready (in_app_enabled=center, push_enabled=phone, min_severity, quiet_hours). No migration — client screen only, relabel channels (알림함/푸시)+explainer.
- ① join pipeline: research → design → revised wireframe → OWNER APPROVE → build. Do NOT build blind.
① DESIGN (research ae03941d, live-DB-verified, wireframe 3rd-pass):
- Reframe MATCHES existing arch:
sessions_select_publicis independent ofclubs.is_discoverable→ entry is already session-first (guest attends public sessions). Not a foreign concept. - KEEP
club_join_requestbackbone (00213/00320/00321/00328/00329 — 5 migrations hardened, RPC-only, correct state machine). Extend, don't replace. - Guest attendance ALREADY queryable:
rsvps(universal, works even if attendance_tracking_enabled=false) +attendance_records(richer, gated on that flag) +manner_tags(LIVE member→guest praise, NO membership check either side). None surfaced in UI yet. match_score_callshard-unanimity = WRONG epistemics for subjective admission (one dissenter blocks popular candidate). Use existing owner/admin authority model.invitestable NOT dead (contra ps6 doc) — it's the phone/Kakao share-a-friend mechanic. 3 join primitives coexist (invite_code / invites / club_join_request).- MVP = near-zero new tables. NEW:
private.has_attended_club_session()helper;public.get_prospect_attendance_summary()read-model RPC (attended/first/last/manner counts/co-attendees, auth-gated owner|admin — research §D full sketch);nominate_for_membership()admin RPC;club_join_request.sourcecol ('self'|'admin_nominated', additive DEFAULT 'self'). EXTEND:apply_to_club+ attendance gate. - LIVE BUG (ship regardless of ①): M4 guest-app capacity/status gate —
decide_guest_application/auto_approve_guest_application_if_needed(00311) still upsert aconfirmedRSVP without checking session status/max_players → over-cap "approved" pollutes attendance counts. ps6 §3.5 fix never shipped. - HELD ON OWNER DECISIONS: ⓐ nominate directionality (admin unilateral vs prospect-accept; research leans accept re: dues consent) · ⓑ self-apply survive (gated) vs nomination-only · ⓒ explicit vouch table now vs passive manner_tags. Defaults OK for: threshold (≥1), message-field (keep), invites-gate. My rec surfaced in wireframe: 총무-단독 + self-apply-gated + manner-tags-first.
- Do NOT ship ① read RPC before the UI is greenlit (RPC w/o consumer = dead code, the anti-pattern the audit fights).
- Wireframe live: artifact 97e2f841 (3rd pass).
SHIPPED 2026-07-12 (wave-3 client OTA + owner-feedback backend):
- Client OTA group
5e985004(preview, commit 7d0ad0bf, JS-only, baseline sourceCommit advanced c3d706e6→7d0ad0bf, native diff empty): mechanical batch (PS8 push-tap routing convergence + admin-count SSoT + signalMatchesScreen map, PS2 stale-hold invalidation, U4 reject-confirm, PS10 records bugs, ICU→plainStringCompare 14 sites) + 3.8 signal copy (club_unarchived enum + memberLeft/settingsUpdated/unarchived i18n ko+en + signal-copy body-args).send-pushedge fn redeployed (uniform{signalId,type,category,severity,context}payload). - Prod migrations pushed + confirmed at remote 00413: 00410 club lifecycle signals, 00411 dues status auto-derive, 00412 club_bank_accounts member-only + purpose_catalog, 00413 guest-app capacity gate. All 4 locally validated by probe; three bugs caught+fixed pre-ship: 00410 dedup-key collision (agent, empirically), 00413 CASE→rsvp_status enum cast, 00412 purpose_catalog min_age NOT NULL.
- Client enum LED the migration (OTA published before prod-push) per enum-drift discipline.
- STILL HELD (owner-gated): ① join pipeline decisions ⓐ directionality / ⓑ self-apply / ⓒ vouch; ③ actor (총무-records vs member-self-report); client screens ②③④; profiles slice 3; Wave-4 guardrails; PS12 streamline.
Wave-4 client build — owner chose RICHER options (2026-07-12, engaged)
Owner reviewed v3 wireframe (artifact 97e2f841: ① full pipeline compact-list+detail-page, ② push-only, ③ selectable chips) → AskUserQuestion decisions:
- ① ⓐ member-vouch + manager decides · ⓑ both gated-self-apply + manager-nominate · ⓒ dedicated recommend action (new table, NOT manner-tags-only).
- ③ member self-reports + manager confirms (attest→confirm like session_payments).
DONE: ② notification-settings screen (agent a9e9b08) — push-only (알림함 always-on badge, per-category push toggle + shared quiet-hours), claimed dead route /settings/signal-preferences, upsert signal_preferences (in_app_enabled forced true), yarn check green 3114. NOT committed (intermingled with ③-partial in shared index.ts).
③ backend 00414 (building, agent a6e5e717): payment_history ADD confirmed_at+confirmed_by; BACKFILL confirmed_at=created_at (PROD-DATA critical); sync_dues_paid_amount sums confirmed-only + fires INSERT OR UPDATE; overpay guard confirmed-only; member self-attest INSERT RLS (own dues, confirmed_at NULL, recorded_by=self); confirm_dues_payment(id) RPC (admin). Signals deferred. Mirrors attest_session_payment (00301).
① backend 00415 (designed, build after 00414 frees DB): club_join_request ADD source ('self'|'admin_nominated'); has_attended_club_session(club,user); NEW membership_recommendations table (club_id/candidate_user_id/recommender_user_id UNIQUE triple; RLS modeled on manner_tags_insert = recommender_id=auth.uid()+active member+candidate eligible; members/admins SELECT; recommender DELETE; pi-schema PI-EXEMPT or purpose_catalog); get_prospect_attendance_summary read RPC (+recommendation_count/recommenders); nominate_for_membership RPC; apply_to_club (00320 body) + has_attended gate. decide_join_request (00320) unchanged.
③ manager-only agent STOPPED — partial dues-client (dues.supabase/mapper/port, use-update-dues+test, club-dues-screen, dues-action-modals, dues-helpers) in tree; REVERT after 00414 + de-thread its shared index.ts/hooks/index.ts/mappers/index.ts lines (keep ②'s). Then build ③ client (member-attest+manager-confirm) on 00414. ④ bank + ①③ clients follow. New signal types → client enum leads.
Wave-4 features SHIPPED (2026-07-12)
All four owner-approved surfaces + their backends are in prod + on the preview OTA. 7 commits de4f79f8..edb74279, one OTA group 539cf107 (JS-only, baseline sourceCommit→edb74279), prod migrations 00414+00415 pushed (remote confirmed at 00415):
- ② notification-settings screen (dcdcdd84) — push-only: 알림함 always-on, per-category push toggle + quiet hours, claimed dead /settings/signal-preferences route.
- ③ dues member-attest → manager-confirm (00414 backend 5b563532 + client 34c25fb5) — member logs pending 송금완료 (confirmed_at NULL explicit, fail-closed RLS), 총무 confirms via confirm_dues_payment RPC, confirmed-only rollup + 00411 auto-status.
- ① join-review pipeline (00415 backend 47939af4 + client 4e586d8d) — compact 가입 심사 list on members screen → detail page (get_prospect_attendance_summary dossier) → 승인/거절; 게스트 지명 (nominate); gated self-apply. member-recommend HOOKS shipped, SURFACE deferred (no eligible-guest browse RPC — needs a follow-up migration).
- ④ club bank-account (00412 already live + client edb74279) — BankSection admin edit + member dues-screen card; OTA-safe copy (selectable Text + web navigator.clipboard, NO expo-clipboard). One-tap native copy DEFERRED to a future EAS build.
Rigor: every backend probe-validated (00414 attest→confirm re-verified independently; 00415 auth-gate blocks non-member); each client yarn-check + lint:strict green + independently re-verified; agents caught real issues (00414 frozen-DEFAULT + admin-path regression, 00415 needless schema-grant via negative-control, ③ CASE cast contract, ① 세션→일정 house-rule, ④ ActionButton-in-Card COMP-3). Owner-deferred follow-ups: member-recommend browse surface (RPC), one-tap native copy (expo-clipboard + build).
Signal completeness + records UX + member-recommend surface + G7 RLS hardening + discovery UX (2026-07-12)
Continuation batch after Wave-4, owner asleep. 6 commits 6c5ba0c0..46231e21 (HEAD bd450979), prod migrations 00416-00418 pushed (supabase db push, remote confirmed synced), preview OTA group 0c2d3bc6-6eeb-4f1f-aa8a-3e53c7c9552c (runtime 0.6.3, JS-only — lastCompatibleSourceCommit stays pinned at edb74279):
- 6c5ba0c0 — 00416 signal completeness: new
manner_tag_receivedsignal (AFTER-INSERT trigger onmanner_tags→ taggee; the type existed but was never emitted) +notify_dm_recipientnow writescontext.threadIdso DM push taps actually route to the thread (getSignalRoutealready read it, the field was just empty). i18nsignal.trust.mannerTagReceived(ko/en). Migration count 408 (per commit body). - 4c871839 — 3.7 records UX:
TierInfoSheetrebuilt onto Tamagui$-token primitives (tier-color dot stays a style escape hatch, same constraint asmedalColor). New 내 순위 (MyRankRow) footer row on all 3 leaderboard tabs (club/region/global — NOT 시즌), computed client-side from data each tab already fetched, shown whenmyRank > 50(>100th-national flagged for a future masking-safe RPC). Unified cause-aware zero-match empty-state selector (selectRecordsEmptyState) replacing 4 ad-hoc empty-state fragments on the 기록 screen. Monthly-challenge bridge extracted touseMonthlyChallengeBridge, mounted at app root (apps/mobile/app/_layout.tsx, enabled-gated pre-auth) so unlocks fire even for a user who never opens 기록; the 기록-screen call site dedupes against it via the TanStack query-key cache (zero net queries when both mounted). - b157ccf0 — 00417
get_recommendable_guestsbrowse RPC (SECDEF, member-callable): returns non-member guests the caller has co-attended a confirmed session with, plus whether the caller already recommended them — the eligible-guest query the 00415 join-review agent deferred for lack of. Probed: co-attended non-member returned, non-co-attended guest excluded, non-member caller blocked. - fd1283d3 — member-recommend browse surface (①ⓒ): new "추천할 만한 게스트" section on
club-members-screen.tsx, gated on active membership (NOT admin) — listsget_recommendable_guestsrows with a 추천/추천됨ActionButtontoggle wired to the existing recommend/unrecommend mutations, invalidating both the admin dossier and this list soalreadyRecommendedflips immediately. NewRecommendableGuestentity + browse hook + row-model. Already updatedscreen-blueprint.mditself as part of this commit. - 9a7745c1 — 00418 closes the 7 WITH-CHECK gaps the G7 completeness guardrail (pgTAP
00027) surfaced:match_setsCRITICAL —USINGwas never correlated tomatch_sets.match_id, so any club admin could write ANY match's sets system-wide; now correlated in bothUSING+WITH CHECK.payment_history+inter_club_challengesmoved toprotect_columnstriggers (mirrors 00404's session_payments pattern);club_events/friendshipsgained realWITH CHECK;dm_threads/player_monthly_statsnarrowed toUSING(false)(RPC/service-role-only, no client writer). 00027 KNOWN GAPS shrank 7→0; stale 00017 also fixed (00415'sapply_to_clubattendance gate needed a seeded confirmed RSVP). Verified: 21 files / 213 pgTAP assertions PASS; match_sets cross-tenant write blocked first-hand;generated.types.tsunchanged (policy/trigger-only). - 46231e21 — 3.6 discovery UX (PS9 remainder):
DirectoryVenueCardgained a 미확인 confidence-honesty badge (mutually exclusive with 인증됨, mirrors the venue-directory adapter's own corroboration predicate exactly, computed locally soReact.memostill holds). NewselectVenueDirectoryEmptyCause()splits trust-gated (default corroboration filter excluded results — one-tap "미확인 포함" CTA flipsverificationto'all') from genuinely-empty. Naver deep-data (naverBusinessHoursper-day rows + top-5naverReviewKeywordsbadges) surfaced on the venue directory detail screen from existingnaver_placeobservation rows already fetched there — no migration, no new round-trip. New sharedRegionDistrictFiltercomponent +region-district-filter.tsutil (parameterized separator — venue matches a hyphen-composite DB slug, pickup uses a pure client-side key) unifies the pickup and venue-directory filter bars, removing ~90 duplicated lines. V3 reopen (closedAt/closureReason) data-plumbed ontoDirectoryVenueonly — no button/banner built, sincesubmit_venue_correction's closed branch andreport_venue_datahave no reopen path yet (needs a migration first). yarn check green (typecheck + lint:strict + 3216 tests, 0 failures).
One OTA publish for the whole batch: the 3 client-touching commits (4c871839, fd1283d3, 46231e21) are all covered by preview group 0c2d3bc6; the 3 backend-only commits (6c5ba0c0, b157ccf0, 9a7745c1) shipped via the prod migration push instead, no bundle involved.
Shippable-tail audit — PS5/PS6/PS8/PS10 (2026-07-12)
Continuation batch after the signal-completeness/records-UX/G7 wave, owner asleep. 4 commits 29a1151b..6ba7a04a (HEAD 6ba7a04a), prod migration 00419 pushed (supabase db push, remote confirmed synced), preview OTA group cc8e7487-6b73-4ddb-8bd2-6561d6727b16 (runtime 0.6.3, JS-only — lastCompatibleSourceCommit stays pinned at edb74279, build 37ac992e remains native-identical):
- 29a1151b — 00419 PS5:
private.session_completed_record_attendance()now bumpsprofiles.total_sessions_confirmedon positive PARTICIPATION EVIDENCE (v_in_match OR (v_tracking_enabled AND v_played)) instead of the rawattendance_tracking_enabledflag. Owner decision, explicit: a played matchup counts even in a club that doesn't track attendance — a played game IS the evidence — while a pure-social untracked session and a tracked no-show still don't count. Not a regression fix (00127 designed the bump ungated by original intent; 00318 reproduced it ungated too); this is an owner-directed evidence-based refinement that decouples the lifetime activity counter (feedsrecalculate_trust_tier's 5/20/50 session-count ladder) from the club's opt-in attendance ledger. Also adds the missingmanner_tagsAFTER-DELETE trust-tier recalc trigger (private.manner_tags_after_delete_recalc) — 00125's AFTER-INSERT sibling (manner_tags_after_insert_recalc) had no DELETE counterpart, so withdrawing a tag left the taggee's tier stale/overstated. Verified: 5-case participation-evidence probe table + delete-recalc probe; pgTAP 21 files / 213 assertions green. - 9f8852c8 — PS10 records leftovers + PS6 roles: R8 deterministic leaderboard tiebreak (
profile.supabase.ts— plain-stringidsecondary sort afterelo_rating DESC, neverlocaleCompare; the RPCRETURNS SETOF jsonbso PostgREST has no column to chain a client.order('id')onto; the LIMIT-boundary tie still needs a SQLORDER BY id, flagged as a follow-up migration); R10partnerAvatarUrladded toMatchHistoryEntry(use-match-history.ts) — data-layer only, no consuming row renders it yet; R11 removed deadeloToNtrp/eloToUtr/eloToKoreanDiv; R12 routedelo_changed/tier_promoted/tier_demotedsignals to/records; PS6 M6 sharedgetRoleLabel(newpackages/app/src/utils/role-labels.ts) — fixedtransfer-ownership-modal.tsx's raw-enum role text (Match_director) and sliced-UUID name fallback. - b0062d92 — PS8:
session_payment_confirmedsignal-category accent →$success(was uniformly$error, so a successful payment rendered red) via a type-level override ongetSignalCategoryAccent;session_payment_submittedstays warning (still a todo-band "confirm receipt" ask for the recipient). Retired the client-localuseScheduleSessionReminderpath now that server-side reminders are verified live end-to-end (generate_session_reminders→ daily cron →send-pushedge fn → Expo).
yarn check green on every commit (typecheck + tests + lint:strict). Note: this ledger entry corrects one provenance detail from the shipping summary — the manner_tags AFTER-INSERT trust-tier-recalc sibling is migration 00125, not 00416 (00416 added a distinct, independent AFTER-INSERT trigger on the same table for the unrelated manner_tag_received signal — verified against both migration files directly).
Batch 2 quick-wins (2026-07-12)
Small continuation batch after the PS5/PS6/PS8/PS10 shippable-tail audit, owner asleep. 2 commits 65920ebc..c9d215da (HEAD d405fa51), prod migration 00420 pushed (supabase db push, remote confirmed synced), preview OTA group 6b146908-f6b1-48ee-89a5-943964c14832 (runtime 0.6.3, JS-only — lastCompatibleSourceCommit stays pinned at edb74279, build 37ac992e remains native-identical):
- 65920ebc — 00420: (1)
get_public_profile_leaderboardORDER BY gains, p.id ASC— deterministic tiebreak at the LIMIT boundary, completing the client-sort half of PS10 R8 shipped in 9f8852c8; (2) SECDEF grant hygiene — REVOKE EXECUTE FROM PUBLIC/anon/authenticated on 3 private trigger fns (session_completed_record_attendance,manner_tags_after_insert_recalc,recalculate_trust_tier) that kept Postgres's default PUBLIC ACL because 00209's revoke was public-schema-only. Not a live exposure (private schema, trigger-invoked only) but closes the CLAUDE.md SECDEF-revoke rule gap. Probe-verified first-hand: onlypostgresholds EXECUTE,SET ROLE authenticated→ 42501, triggers still fire. - c9d215da — removed dead
scheduleLocal/cancelSchedulednotification-port methods (+ both adapters; zero callers post PS8-4.2); create-clubgetRegions()now derives from canonicalSIDO_KEYS(chip order now matches every other region picker; keptt().regionLabelsas the primary label source to avoid Korean-leaking the en locale).
One OTA publish for the batch: d405fa51 advanced the preview group cc8e7487→6b146908 (JS-only; sourceCommit stays edb74279).
Open owner-decision tail (carried forward, unchanged by this batch): R10 match-history avatar render (UI decision — the row is currently all-text, partnerAvatarUrl sits unused in MatchHistoryEntry); 일정 리마인더 toggle (settings-screen.tsx's notifySessionReminders — wire it to the now-live server-side reminder gate, or remove it, now that useScheduleSessionReminder is retired); PS9-V2 resolver + M1 RSVP-tier-gate (rsvps_enforce_tier uses cross-pool elo_rating rather than a format-aware rating — algorithm/product decision, see docs/architecture/wiring-plans-2026-07-backend.md); V3 reopen (closedAt/closureReason plumbed onto DirectoryVenue, no UI — needs a wireframe); edit-profile region SoT (same SIDO_KEYS dedup just applied to create-club, flagged by the cleanup agent, not yet done).
Batch 3 — reminder-toggle wiring + R10 avatar stacks + edit-profile SoT (2026-07-12)
Owner re-engaged and picked all four remaining workstreams; this batch shipped the two owner-decided items that were ready. 3 commits 7f222deb/c5e9c409/e9348d7e + baseline fd71e6e9, prod migration 00421 pushed, preview OTA group 91de070c-30cc-4398-a162-c436e62b2052 (runtime 0.6.3, JS-only, sourceCommit stays edb74279):
- 7f222deb — 00421: gate BOTH session-reminder crons (
generate_session_reminders24h +signals_tick_session_starting_soon2h) onuser_preferences.notify_session_reminders. The 일정 리마인더 settings toggle already round-tripped that DB column (00048), but no server emitter consulted it, so post-PS8-4.2 it controlled nothing. Owner picked "wire it."signal_preferenceswas the wrong lever (per-category, owned by the 알림 설정 push screen). New pgTAP00028(8 assertions, opt-out/opt-in/no-row-default × both crons); live probe confirmed toggle-off → 0 emitted, toggle-on → 1. - c5e9c409 — R10: owner picked "full avatar stacks." The all-text match-history row now shows my-side + opponent-side
AvatarStacks (reused the existing@twomore/uiprimitive — same as the club-card cluster). Threaded one newmyAvatarUrlontoMatchHistoryEntry;React.memoprop surface unchanged. - e9348d7e — edit-profile region SoT: replaced the hand-rolled
REGION_KEYSarray with canonicalSIDO_KEYS(same dedup as create-club; keptt().regionLabelsprimary to avoid Korean-leakingen).
Backend-queue batch — V3 reopen / PS9-V2 resolver / M1 tier-gate / SECDEF sweep (2026-07-13)
Continuation batch after Batch 2 quick-wins, owner asleep. 4 commits 503c53f4..7b5585c9 (HEAD c8d14a3d after the OTA-bump), prod migrations 00422-00425 pushed, preview OTA group 8204cb6e-9a86-47f5-a018-22d786b93e4d (runtime 0.6.3, JS-only — lastCompatibleSourceCommit stays pinned at edb74279, build 37ac992e remains native-identical, verified via empty nativeSensitivePaths diff against baseline d6817b48):
- 503c53f4 — 00422 PS9-V2 venue resolver "human observation wins":
resolve_venue_factshad folded host/manualvenue_source_observationsinto the same majority vote as scraped sources, so a manual/host correction was outvotable by 2 scraped rows. Adds a Layer-2 straight-override (host>manual, all 11 facts) between the scraped-consensus compute and thevenue_correctionsledger override (ledger still wins over Layer-2). + 00423 V3 venue reopen:submit_venue_correction'sclosedfield now accepts any boolean (wastrue-only) and runs through the SAME reputation-weighted trust gate as every other fact (trusted/veteran → instant apply, else needs a 2nd independent agreement), clearingclosure_reasonon a successful reopen; the agreement tally now additionally filtersAND proposed_value = v_value(a probe proved a close-voter's later reopen would otherwise miscount against the close tally). Closes the "algorithm/product decision" tail flagged in the Batch-2-quick-wins entry above. Verified first-hand: Layer-2 + value-filter present in live DB, ACLs locked, pgTAP 22 files/221 assertions green, yarn check 3201 tests. - c6bde205 — V3 client (owner picked "banner + tile" from the wireframe options): closed venues on the venue directory detail screen (
directory-venue-detail-screen.tsx) now show a top warning banner ("폐업으로 표시된 코트예요" + closure-reason line), a "재개 제보" tile appended to the existing 3-tile action row (call · directions · save), and terminal-state muting (opacity=0.55on the sections below, same treatment as an archived club card). Both triggers open a ModalPanel confirm then submit{field:'closed', value:false}via the existinguseCorrectVenueFacthook — matching 00423's server contract exactly. New venue i18n keys (ko+en). Closes the "V3 reopen ... no UI" tail flagged in the Batch-2-quick-wins entry above. yarn typecheck + 3201 tests + lint:strict green. - d6122d4f — 00424 M1 format-aware RSVP tier gate:
private.rsvps_enforce_tiergated eligibility on cross-poolprofiles.elo_rating, so a doubles session could admit someone on an unrelated singles rating and vice versa; now branchessingles_elo/doubles_elobysessions.format(singles_doubles/mixed_doublescollapse to the doubles pool, matching rotation-presets' own binary collapse). Grandfather skip untouched (already-confirmed RSVPs are not retroactively re-evaluated, per owner + 00230 precedent — shipped silently). Client catch: the REAL CTA-blocking badge is an ad-hoctierBlockedderivation insession-detail-screen.tsx(the plan's named field,session-info-model'sviewerEligibleForTier, is test-only/unrendered) — both were reading cross-pooleloRating; fixed to the pool-specific elo so the display matches the authoritative server gate. Probe 5/5 (doubles+low-doubles_elo blocked, singles+high-singles_elo allowed, confirmed RSVP grandfathered); pgTAP 22 files/221 assertions; yarn check 3202 tests. - 7b5585c9 — 00425 SECDEF hygiene sweep: of 108
private-schema SECURITY DEFINER functions, 51 still carried Postgres's default PUBLIC EXECUTE grant (00209'sALTER DEFAULT PRIVILEGES REVOKEis public-schema-only, soprivate.*functions keep the hardwired owner+PUBLIC default) — revoked via a dynamic DO-block driven by the exact audit predicate (no hand-typed signature list); each of the 51 confirmed safe by three independent checks (pg_depend trigger-only edges, nested-SECDEF caller chain, no client/edge.rpc()site sinceprivateisn't inconfig.toml [api] schemas). One documented exemption:has_attended_club_session(00415) stays PUBLIC-executable because it's invoked insidemembership_recommendations_insert's RLSWITH CHECK, evaluated as the invoking role. pgTAP00026extended to assert noprivateSECDEF fn grants anon/authenticated/PUBLIC EXECUTE (exemption allow-listed) so the discipline can't lapse silently. Finding, live-verified:ALTER DEFAULT PRIVILEGES ... REVOKEdoes NOT suppress Postgres's hardwired default PUBLIC-EXECUTE ACL when no prior GRANT row exists — 00209's and 00393's "closes it at source" claim is unproven; real prevention is the inlineREVOKE EXECUTE ... FROM PUBLICon each function plus this mechanical guard. Verified: post-sweep only the 1 exemption remains loose, pgTAP 22 files green, trigger paths still fire.
Deploy: prod migrations 00422-00425 pushed; client half OTA'd via preview group 91de070c→8204cb6e (JS-only; sourceCommit stays edb74279).
Tail closed by this batch (both carried from the Batch-2-quick-wins open list above): PS9-V2 resolver + M1 RSVP-tier-gate, V3 reopen UI. Still open, unchanged: R10 match-history avatar render; 일정 리마인더 toggle; edit-profile region SoT dedup.
Profiles-PII slice-3 prerequisite + PS12 dead-code first pass (2026-07-13)
Small continuation batch, DOC-only entry (no prod migrations this round). 3 commits 8af36b26/8d06476d/b1f6c8db + baseline e8180fb7 (HEAD b1f6c8db = origin/main), preview OTA group 19fa7638 (runtime 0.6.3, JS-only — lastCompatibleSourceCommit stays pinned at ccbedad1, per the release commit's empty nativeSensitivePaths diff):
ccbedad1 (G6, backfilled — the immediate predecessor that produced the "79 unused files" figure
8d06476dacts on): wired knip dead-code detection into the repo (knip.jsonconfig +yarn knipscript) and removed 5 already-dead tier-export symbols surfaced by the first run. Its OTA-baseline siblingb7d9edd8advancedlastCompatibleSourceCommitedb74279→ccbedad1under the native-irrelevant clause (knip is a devDep — thepackage.json/yarn.lockdiff is provably native-neutral, no OTA published). This is why the source baseline readsccbedad1, notedb74279, from this section onward.8af36b26 — profiles slice-3 PREREQUISITE:
friendship.supabase.tsanddm.supabase.tswere the only 2 adapters still doing a raw other-userprofilesread — the PS1 design doc's claim that all other-user reads were bounded to the 7profile.supabasemethods was stale. Both routed onto the 00394 masking RPC (get_public_profile_views);dm.supabase.ts's 3 FK-embed call sites (getThreadsPage/getThread/getThreadParticipants) re-synthesize the embed shape from an ids-only fetch + one batched RPC call (mapper unchanged — masked jsonb matchesProfileRowfield-for-field). CLOSED A LIVE PII LEAK: the old friendship select over-fetchedkakao_id/gender/districtover the wire (unused) and served region UNMASKED regardless of the friend's visibility setting. yarn typecheck + 3182 tests + lint:strict green. Shipped via preview OTA group19fa7638(e8180fb7).- HELD, not applied by this commit: the
profiles_selectRLS narrow itself (DROP POLICY ...; CREATE POLICY ... USING((select auth.uid()) = id)) — unchanged by the existing HARD HOLD at the top of this doc.
- HELD, not applied by this commit: the
8d06476d — PS12 Stage A first pass: deleted 69 grep-confirmed dead files (knip 79→10), each with a documented supersession reason (mostly hooks/stores/schemas/barrels already superseded by a canonical replacement elsewhere in the codebase — e.g.
use-weather→use-session-weather,use-ranking-data/use-in-club-ranking/use-season-standings→use-ranking,use-delete-club→useArchiveClub). Verified: independent full typecheck (0 errors, all workspaces) + 3200 tests incl. ArchUnitTS + lint:strict. Runtime-neutral (dead code only) — no OTA.- Deliberately left (false positives / policy calls, not touched):
config/i18n/mobile.ts(MetroresolveRequestswaps it in at runtime, invisible to knip/grep),types/branded.ts(P5 infra), the 7application/*.usecase.ts(an ArchUnit hex-layer question),use-invites.ts, anddomain/index.ts's ~387 forward-declared schema re-exports (the documented flat-now/generalized-model-held norm — an owner policy call, not mechanical cleanup).
- Deliberately left (false positives / policy calls, not touched):
b1f6c8db — PS12 Stage C: 18 unit tests backfilled across 3 previously-untested high-value units —
use-monthly-challenge-bridge(uid fallback,ChallengeProgressInputderivations, key-gate forwarding the real userId not the''fallback),role-labels/getRoleLabel(all 4 role→label branches incl.match_director→경기이사 + the fallback),use-recommendable-guests(enabled/disabled, query-key scoping, clubId-change refetch). The 4th named gap (join-review mappers) was a non-gap — already covered inline byjoin-review.supabase.ts's own test, so no duplicate was added. yarn typecheck + all tests + lint:strict green.
Held / gated tail (unchanged by this batch, carried forward):
- profiles slice 3 — unchanged HARD HOLD (top of this doc):
profiles_selectstaysUSING(true)until the slice-2 masking-RPC client group (ddd74020) AND this prereq's group (19fa7638) have both baked across the user base. - PS12 Stage B (owner decisions, not yet built):
club_alertsretirement (dead reminder-cron table, zero UI consumers per the B5/B7 audit findings), theapplication/*.usecase.tslayer's fate (hex-layer question flagged by 8d06476d), referee-transfer removal (fully-orphaned feature per U4 — 5 hooks, 0 consumers) — plus thedomain/index.ts~387-line barrel-trim policy call flagged in the same commit. - PS12 12A / 12C / 12D (E2E-gated, per
docs/architecture/solutions/ps12-engineering-streamline.md): the SSoT conformance sweep (12A), the hex-architecture/SoC conformance + orphan-deletion pass (12C), and the integration/E2E test-suite backfill (12D) all explicitly depend on PS1–PS11 being shipped AND E2E-confirmed first — "must not start early" per the design doc. Only a slice of 12B (knip dead-export retirement) and a slice of 12D (unit-test backfill) were pulled forward opportunistically as Stage A / Stage C above; the E2E-gated slices remain untouched.
Tooling finding — do NOT run yarn lint:ratchet to "clean" the baseline yet: it silently regenerates eslint.ratchet.config.mjs down to 0 rules / 0 waivers, even though 103 real violations still exist. Root cause, confirmed first-hand (temporarily emptied the ratchet, measured, restored via git checkout, tree verified clean before and after): the committed ratchet currently waives 4 rules (@twomore/no-padding-top-in-features, @twomore/no-inline-style-with-vars, @twomore/no-stylesheet-create, @twomore/no-raw-rn-pressable-in-features), which is why yarn lint:strict passes clean today (1580 files, 0 errors/warnings). But scripts/generate-eslint-ratchet.mjs collects violations by running plain yarn eslint ., which ALREADY has the current ratchet applied (eslint.config.mjs imports and spreads it) — so the generator can never see the violations its own prior output is suppressing. With the ratchet emptied, those same 4 rules resurface across 53 files / 103 errors (no-padding-top-in-features:44, no-inline-style-with-vars:43, no-stylesheet-create:14, no-raw-rn-pressable-in-features:2) — exactly what lint:strict would fail on the moment a regenerated empty ratchet got committed (the 53-vs-84-listed-files gap also suggests ~31 stale waiver entries already sitting in the committed list unpruned). The generator is self-blinding: it can only ever shrink or hold the committed waiver list, never verify it's still accurate, so "regenerate to shrink after fixing" is only safe immediately after fixing the specific files being dropped — running it blind risks silently discarding the other files' legitimate waivers. Needs a fix (diff old vs. new waiver sets per rule and refuse to drop a file that wasn't actually touched, or lint each ratcheted file with its waived rule temporarily re-enabled) before it's safe to run again.
PS12 Stage B — dead-code retirement (2026-07-13)
Owner picked (AskUserQuestion) 3 of the 4 Stage-B items: club_alerts retirement, the application/*.usecase.ts layer's fate, and the domain/index.ts barrel trim (declined referee-transfer, which stays held). 2 code commits 8daac45e/0514a625 + the prior doc commit 343657a1 (HEAD 0514a625 = origin/main). No prod migration, no OTA — all three are runtime-neutral dead-code removals. Each scoped by a fresh-context Sonnet agent, then independently re-verified by grep before commit (the usecase/barrel research's "zero callers" framing was imprecise — its cited refs were def-file + re-export-chain internals, not consumers; confirmed harmless by hand).
- 8daac45e —
club_alertsclient CRUD/read path removed (4 deletions: port/adapter/mapper/entity + 5 edits: registry/query-keys/domain-index/ko+en settings i18n). The "retire club_alerts" task was a misnomer — the mechanism is NOT dead. The build agent proved via a live end-to-end firing test (reset DB → insert club + joining member → watchedtrg_alert_member_joinedwrite aclub_alertsrow → the signals bridge fan it out to a realsignalsrowtype='club_announcement') that the DB half — theclub_alertstable + 5 event triggers (member-joined / session-created / match-completed / event-created / post-created) +signals_club_alert_bridge_trigger+private.create_club_alert()— is LIVE plumbing behind the shippingclub_announcementsignal type rendered in the Notification Center. So no migration 00426 was created (nothing to retire at the DB level; the cron writergenerate_dues_reminderswas already dropped in 00405), and theclub_alerts/club_alert_readstables were left intact. Only the confirmed-zero-consumer client surface (original consumersClubAlertBanner/useActiveNoticelong gone) + the orphanedclubAlerts:i18n namespace were removed. Verified first-hand: 0 dangling refs to any removed symbol,signal.club.announcement.*i18n preserved,yarn checkgreen. - 0514a625 — 7 orphan usecases + the abandoned AppError hierarchy. (1) application/: deleted the 7 zero-caller
*.usecase.ts(check-achievements, create-club, join-club, generate-dues, submit-score, create-session, manage-rsvp), each superseded by a shipped direct mutation hook; keptusecases/host-pickup-game.usecase.ts— the one genuine multi-repo orchestration case (real calleruse-host-pickup-game) the shared-client-architecture doc's carve-out is for. ArchUnitTS asserts import DIRECTION only, never existence, so the deletions break no fitness rule (verified: architecture-archunit green). (2) domain/errors: deleted the entireapp-error.tsmodule + its 3-layer re-export chain (errors/index.tsL1-10 + thedomain/index.tsbarrel block + theErrorKindre-export) — a richer-but-never-wired competitor to the LIVE legacyDomainErrorhierarchy (base.adapter.ts+ public API use the legacy one). Recoverable from git if ever revived; single-source-of-truth. Deliberately did NOT bulk-trim the barrel — research + first-hand check confirmed the other ~700 knip-flaggeddomain/index.tslines are TYPE-3*Schemapolicy (enforced against the entity file, not the barrel) + forward-declared held models (e.g. the Eligibility block, now partially wired via M1/00424), not orphans. The AppError chain was the one genuine orphan.
Durable gotcha — the pre-push hook typechecks the LIVE working tree, not HEAD. Backgrounding a git push while mid-edit (had git rm'd app-error.ts but not yet fixed errors/index.ts's import of it) made the hook's yarn typecheck fail on the transient inconsistent tree → push rejected. Never background a push with uncommitted in-flight edits; commit to a consistent tree first.
Stage-B status: the 3 owner-picked items are DONE. Referee-transfer removal (the declined 4th) stays held pending product sign-off. The barrel is now as trimmed as it should be (the schema/held-model bulk is intentional). PS12 12A/12C/12D remain E2E-gated as above.