Production Readiness Review
Status: Active
This doc bundles every dimension of production readiness for the TwoMore v2 app into a structured ledger. Each capability is rated by current state, gap to close, and which release track it belongs to.
Companions:
docs/pre-launch-checklist.md— externally-blocked launch gates (CPO contact, counsel review, iOS builds, lbsc.kr filing, branch protection)docs/external-blockers.md— single routing register for owner/vendor/platform blockersdocs/rebuild-log.md— chronological commit-by-commit narrativeCLAUDE.md— operational state + rule book
Release tracks (cumulative):
- v1.0 — Launch blocking. Anything that violates Korean law, leaks PII, or makes the app unusable on first install must be GREEN before public launch.
- v1.1 — Operational hardening. Observability gaps + crash-blind paths + post-launch incident-response prep. Don't gate launch but close within 4 weeks.
- v1.2 — Scale prep. Patterns that hold at hundreds of users but break at thousands.
Status legend: ✅ green · ⚠️ yellow (partial / latent) · ❌ red (must fix) · ➖ optional
Last reviewed: 2026-06-29 (R1 external credential/domain/build gates deferred to Production Readiness/R4)
0. Deferred R1 External Gates — v1.0
Owner decision, 2026-06-29: the remaining R1 release-readiness blockers that require external authority are tracked here as Production Readiness/R4 work. They are deferred from active R1 source implementation, not accepted or waived. Future R1 agents should still run yarn report:r1-release-readiness for visibility, but should not reopen these items as implementation work unless the owner has provided the required credentials, hosting access, build authority, or device-smoke environment.
| Capability | Status | Gap | Track |
|---|---|---|---|
| App-link identifiers and live apex deployment | ❌ | TWOMORE_APPLE_TEAM_ID, TWOMORE_ANDROID_SHA256_CERT_FINGERPRINTS, and live https://twomore.app/.well-known/* direct JSON serving are missing. Current live apex endpoints redirect to www, then to /auth/login. See Pre-launch #12. | R4 |
| KakaoTalk Share production credentials | ❌ | Six TWOMORE_KAKAO_TEMPLATE_* IDs and a Kakao native app key for the build/deploy environment are missing. Static template metadata and native adapter wiring exist, but KakaoTalk rendering/return proof is not accepted. See Pre-launch #13. | R4 |
| Compatible preview build and OTA baseline | ❌ | Current source has native-sensitive app-link/Kakao changes after the recorded OTA compatibility baseline. A compatible preview build, install smoke, and apps/mobile/ota-compatibility-baseline.json update are required before further app-source OTA claims can be made from current source. | R4 |
| iOS preview build and device smoke | ❌ | No iOS preview build evidence exists. Apple Developer credentials, iOS EAS credential setup, install smoke, and app-link/Kakao device proof are required before public launch. See Pre-launch #4. | R4 |
| R1 consolidated hosted/device evidence packet | ⚠️ | R1 source/custom-scheme evidence is strong, but the final packet must be rerun against the candidate preview build/source state after the external app-link/Kakao/build gates above are available or explicitly launch-deferred by the owner. | R4 |
1. Regulatory compliance — v1.0
| Capability | Status | Gap | Track |
|---|---|---|---|
| PIPA §15 consent ledger | ✅ | user_consent table + purpose_catalog + policy_versions (00167-00173) all live. useGrantConsent wires onboarding + settings management. | v1.0 |
| PIPA §21 right-to-delete | ✅ | delete_account_atomic SECDEF RPC (00186) — soft-delete + push_token clear + consent revoke + audit write, all atomic. | v1.0 |
| PIPA §22-2 under-14 guardian | ✅ | reject_under14_birth_year BEFORE INSERT/UPDATE trigger (00186) + client-side onboarding block. Defense-in-depth on both sides. | v1.0 |
| PIPA §28-8 cross-border transfer | ⚠️ | Disclosure exists in PrivacyScreen but assistant-drafted text. Needs counsel review. | v1.0 — see pre-launch-checklist.md |
| PIPA §29 PI access audit log | ✅ | pi_access_log + record_pi_access SECDEF RPC (00185) wired into support_get_user_errors, get_user_consents admin branch, export-user-data edge function. | v1.0 |
| PIPA §30 CPO designation | ❌ | Placeholder values still in privacy-screen.tsx:254-257. | v1.0 — see pre-launch-checklist.md |
| PIPA §35-2 data portability | ✅ | export-user-data edge function returns full JSON dump on demand. | v1.0 |
| 정통망법 §50 marketing consent | ✅ | Separate marketing_push + marketing_push_night purpose keys in purpose_catalog. Annual re-confirmation cron at 02:00 KST (00171). Required "(광고)" prefix + 1-tap unsubscribe documented in CLAUDE.md. | v1.0 |
| Location Information Act §9 | ❌ | 위치기반서비스사업자 신고 not filed. Required before GPS check-in goes live to general public. | v1.0 — see pre-launch-checklist.md |
2. Security — v1.0 + v1.1
| Capability | Status | Gap | Track |
|---|---|---|---|
| Service-key rotation hygiene | ✅ | Path B rotation closed 2026-05-19. Edge functions that need service authority read TWOMORE_SECRET_KEY; direct SUPABASE_SERVICE_ROLE_KEY reads are blocked by no-legacy-service-role-key-in-edge-functions. | v1.0 |
SECDEF function search_path hardening | ✅ | Migration 00188 hardened 8 SECDEF functions with SET search_path = '' + fully-qualified identifiers. CLAUDE.md Data rule codifies pattern for new functions. | v1.0 |
| RLS coverage on PII tables | ✅ | All PII-holding tables have RLS enabled with auth.uid-scoped policies. lookup_club_by_invite_code SECDEF wrapper (00146) closed the invite-code enumeration vector. Wave D3 (commit 312ae79, migration 00190) closed the cross-club leak on player_matchups + season_records — per-opponent matchup history + per-season ELO progression now scoped to own row OR same-club member. | v1.0 |
| Edge function rate limiting | ⚠️ | search-venues got an app-level gate. The weather-fetch family (fetch-weather, fetch-living-weather, fetch-medium-forecast, fetch-weather-warnings) still relies primarily on Supabase/platform controls and cron-secret/service-key gating, so KMA-quota protection remains a v1.1 hardening item. | v1.1 |
| Edge function input sanitization | ⚠️ | search-venues got PostgREST .or() sanitizer. Other edge functions accepting user input → DB queries should be audited. | v1.1 (audit pending) |
| Soft-delete read-side enforcement | ✅ | Wave B fixed 7 known sites with .is('deleted_at', null). CLAUDE.md Components rule codifies the contract. | v1.0 |
| PII redaction in diagnostic payloads | ✅ | redactPII helper (14 sensitive keys) wired into all 3 critical-error sinks. 26 unit tests cover the contract. | v1.0 |
| Idempotency keys on mutations | ✅ | Migration 00122 + 00189 cover all mutable tables. Hooks thread keys: useRsvp, useSubmitScore, useSetPostReaction, useVotePoll, useCreateSession, useCreatePost. Use-case hostPickupGame threaded in 2b53c31. | v1.0 |
3. Frontend resilience — v1.0 + v1.1
| Capability | Status | Gap | Track |
|---|---|---|---|
| Offline-queue (Phase 14d) | ✅ | Queue-aware mutations are guarded by require-network-mode-always-for-queueable and require-executor-for-queue-key, with MMKV replay, idempotency dedup, and 5 retries × 7 days. | v1.0 |
| Persistent realtime channels | ✅ | Retired. The app opens zero persistent Supabase Realtime WebSocket channels. Live screens use focus-gated polling, DM uses native refetchInterval, signals use push/cache-bust, and ordinary data uses staleTime + mutation invalidation. Regression guards remain (no-realtime-subscribe-outside-realtime-hooks, require-is-focused-in-realtime-hook, realtime-focus-gate). | v1.0 |
| Focus-gated active subscriptions | ✅ | Clock tickers and live polling use useFocusedEffect/shared ticker primitives so background screens stop work on blur. Future realtime hooks must use the same primitive. | v1.0 |
| Network protocols (A-L) | ✅ | Codified in CLAUDE.md. Per-item bulk fetch (Protocol A) + prop-or-fetch (B) + targeted invalidation (C) + staleTime respect (D) + selector-over-second-fetch (E) + placeholder from list (F) + screen-readiness gate (G) + anticipatory prefetch (H) + foreground/background separation (I) + IA (J) + mapper resilience (K) + pause-on-blur (L). | v1.0 |
| Error visibility | ✅ | captureCriticalError → MMKV crash log (survives process death) + Supabase client_errors + Sentry. Dev panel surfaces recent errors. | v1.0 |
| HINT propagation through error subclasses | ✅ | commit 883e1e4 — every DomainError subclass now propagates Postgres HINT. Tests pin the contract. Fixes the gps_not_rsvpd (42501) silent-drop bug. | v1.0 |
| Mapper resilience (Protocol K) | ✅ | safeMap boundary in base.adapter.ts + safeParse fallbacks in 4 mapper files (commit 30b2609). One bad row no longer crashes a list query. | v1.0 |
| Force-update gating | ❌ | No mechanism. Future runtime-version bump → old clients crash repeatedly with no in-app prompt. | v1.2 — see pre-launch-checklist.md |
| Test coverage on critical mutation paths | ⚠️ | Money paths (useUpdateDuesStatus, session-payment hooks) + auth (useDeleteAccount) + PII (useUpdateProfile, useGrantConsent) → coverage gap surfaced by frontend audit H3+M1, deferred to a future test sweep. | v1.1 |
| Tab navigator crash isolation | ✅ | Wave D2 (commit 48db894): HomeTabIcon wrapped in ErrorBoundary fallback → bare Home icon, so a hook-throw inside the live-badge stack degrades gracefully instead of taking down the entire tab bar. | v1.0 |
| Auto-RSVP offline-queue support | ✅ | Wave D2 (commit 48db894): useToggleAutoRsvp upgraded from raw useMutation to networkMode='always' + offline enqueue branch + addBreadcrumb. 총무 toggling preferences in poor-signal venues no longer silently loses writes. | v1.0 |
4. Backend observability — v1.1
| Capability | Status | Gap | Track |
|---|---|---|---|
| Sentry init + release tagging | ✅ | initSentry captures release + dist from Updates.runtimeVersion + Updates.updateId. Crash reports bucketable by OTA group. | v1.0 |
| Sentry breadcrumb coverage on mutations | ✅ | Wave D4 (commit 80f0a7c): breadcrumbs added on GPS check-in, dues update, consent grant/revoke, account delete. Wave A and earlier already had breadcrumbs on RSVP, score-submit, manner-tags, post-create, score-call. | v1.0 |
client_errors audit log | ✅ | recent_client_errors SECDEF RPC now defaults to last 24 h (migration 00191, Wave D4) and caps p_limit 1–200. Dev panel surfaces in-app. | v1.0 |
| pg_cron health visibility | ⚠️ | dev_inspect_cron_status exists for diagnostics (DEV_USER_ID-gated) but no automated alerting on cron failure. | v1.1 |
| Auth-bootstrap + realtime Sentry blind paths | ✅ | Wave D1 (commit cca734f): 5 session-provider.tsx catches + realtime subscribe CHANNEL_ERROR/TIMED_OUT + realtime backpressure handler-throw + query-lazy-persister manifest-read + 2 ErrorBoundary onError callbacks all now route to captureCriticalError. Previously 100% invisible in production. | v1.0 |
| CI lint:pi-schema gate | ✅ | Wave D1 (commit cca734f): .github/workflows/ci.yml now runs yarn lint:pi-schema on every push + PR. New migrations touching PI tables fail CI unless they INSERT a purpose_catalog row or carry a -- PI-EXEMPT: header. | v1.0 |
| Production OTA channel | ⚠️ | apps/mobile/eas.json has production.channel = "production" and root scripts provide guarded yarn ota:production / yarn ota:production:canary. Until R4 Launch Readiness is accepted, normal OTA publishes go to preview only via yarn ota:preview. First production rollout must follow Release Versioning And OTA Workflow and record source commit, runtime, update group, smoke evidence, monitoring, and rollback pointer in Release Evidence Ledger. | v1.0 |
yarn metrics snapshot publish | ⚠️ | Script produces JSON locally + CI uploads as artifact (90-day retention). Not surfaced as a dashboard. | v1.2 |
5. CI / quality gates — v1.0
| Capability | Status | Gap | Track |
|---|---|---|---|
| Typecheck on all PR/push | ✅ | yarn typecheck in CI workflow. | v1.0 |
| Test on all PR/push | ✅ | yarn test runs full suite. | v1.0 |
lint:strict (--max-warnings 0) | ✅ | Locked at 0/0 across the entire codebase. 52 active @twomore/* rules + recommended-set. | v1.0 |
lint:agents-md | ✅ | Validates AGENTS.md table schema on every push. | v1.0 |
lint:pi-schema | ✅ | Forces every new migration to either INSERT into purpose_catalog or carry a -- PI-EXEMPT: comment. | v1.0 |
verify-no-stale-claims | ✅ | Pre-push hook validates commit message paths exist in the diff. | v1.0 |
Branch protection on main | ❌ | CI is reliably green but main is unprotected. | v1.0 — see pre-launch-checklist.md |
| Supabase type-drift gate | ⚠️ | Workflow exists but opts in via SUPABASE_ACCESS_TOKEN secret. Not set → silent skip. | v1.1 |
| Ratchet-shrink discipline | ✅ | Current yarn metrics snapshot reports 4 ratchet rules with 87 waivers; rules block all new drift while grandfathered files shrink as touched. | v1.0 |
6. Architecture invariants — v1.0
| Capability | Status | Gap | Track |
|---|---|---|---|
| Hex-arch direction enforcement | ✅ | 16 ArchUnitTS fitness tests + ESLint no-direct-supabase-in-presentation + no-supabase-auth-outside-adapter. | v1.0 |
| Domain entity Zod-schema coverage | ✅ | TYPE-3 closed. All 42+ entities export *Schema. Arch-test fs scan enforces new entities. | v1.0 |
| Mutation key contract | ✅ | require-mutation-key rule + ratchet at 0 waivers (cleared 2026-05-04). | v1.0 |
| Freshness contract | ✅ | require-freshness-contract rule + ratchet at 0 waivers. | v1.0 |
| Tamagui-only styling | ✅ | no-stylesheet-create + no-classname-prop + no-nativewind-imports + no-inline-style-with-vars + no-hex-in-style-props + no-numeric-spacing-in-features + no-padding-top-in-features. | v1.0 |
| Bottom-sheet safety | ✅ | no-gorhom-bottom-sheet-in-shared (crashes web) + no-bare-scrollview-in-sheet (gesture-conflict guard). | v1.0 |
| Mock adapter cleanup | ✅ | packages/app/src/__mocks__/ deleted; tests use inline port-shaped stubs. Test data lives in supabase/seed.sql. | v1.0 |
7. Release pipeline — v1.0 + v1.1
| Capability | Status | Gap | Track |
|---|---|---|---|
| EAS Android preview build | ⚠️ | Latest documented Android runtime is 0.6.1, but current source is behind the recorded preview-native compatibility baseline because native-sensitive app-link/Kakao files changed after build 6aa365f6-7814-4250-a708-a27843a919dc. Rebuild/smoke as part of R4 before accepting current source. | v1.0 |
| EAS Android production build | ✅ | Production profile/channel exist; rebuild and smoke current 0.6.1 runtime before listing. | v1.0 |
| EAS iOS preview build | ❌ | Apple Developer enrollment + interactive credential setup pending; deferred to R4 Production Readiness with app-link/Kakao device smoke. | v1.0 |
| EAS iOS production build | ❌ | Depends on iOS preview validation. | v1.0 |
| OTA preview channel | ✅ | yarn ota:preview targets the preview branch/environment and is the normal pre-production OTA target. The script refuses dirty, no-upstream, unpushed-ahead, and behind-upstream source states; significant changes must be classified with Release Versioning And OTA Workflow and recorded in Release Evidence Ledger when an artifact is attempted. | v1.0 |
| OTA production channel | ⚠️ | yarn ota:production and yarn ota:production:canary target the production branch/environment but are launch-gated by scripts/guard-ota-release.mjs until explicitly enabled after R4. | v1.0 |
| App Store Connect listing | ❌ | Bundle ID locked but listing assets not prepared. | v1.0 (alongside iOS build) |
| Play Console listing | ❌ | Apk uploadable but Data Safety / content rating questionnaires not done. | v1.0 |
Ledger of audits
| Date | Audit | Findings | Resolution |
|---|---|---|---|
| 2026-05-19 morning | 4-agent comprehensive (UX/error/security/perf) | ~60 findings, 3 CRITICAL + 5 worst HIGH | Wave A + Wave B + Wave C closed all CRITICAL + HIGH + most MEDIUM. |
| 2026-05-19 afternoon | 3-agent (backend security / frontend resilience / infra observability) | 29 findings: 3 CRITICAL + 9 HIGH + 11 MEDIUM + 4 LOW (deferred) | Waves D1–D4 (commits cca734f, 48db894, 312ae79, 80f0a7c, OTA 777b2d80) closed all 3 CRITICAL + 5 HIGH + 5 MEDIUM. Remaining: weather-edge rate limits (BE H1, deferred to v1.1 budget), SECDEF search_path = '' rewrites for 8 functions (BE M1, body-audit deferred to D5), signals.context display_name PII scrub (BE M3, trigger-body recreate deferred to D5), critical-path test coverage (FE H3 + M1, deferred), cron-failure watchdog (Infra H4, requires external Slack/email plumbing). |
How to use this doc
- Before any commit: check which row(s) of which table the commit touches; mark them ✅ if the gap closes, or add a new row if the commit introduces a new capability.
- Before a release: every v1.0 row must be ✅. v1.1 rows can ship after launch but should close within the v1.1 budget (~4 weeks post-launch).
- When auditing: add a row to the "Ledger of audits" table with the date, scope, finding count, and resolution commits.
- When a regulator question arrives: the regulatory compliance table is the answer key. Each row cites the specific code path + migration that satisfies it.