Skip to content

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:

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.

CapabilityStatusGapTrack
App-link identifiers and live apex deploymentTWOMORE_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 credentialsSix 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 baselineCurrent 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 smokeNo 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

CapabilityStatusGapTrack
PIPA §15 consent ledgeruser_consent table + purpose_catalog + policy_versions (00167-00173) all live. useGrantConsent wires onboarding + settings management.v1.0
PIPA §21 right-to-deletedelete_account_atomic SECDEF RPC (00186) — soft-delete + push_token clear + consent revoke + audit write, all atomic.v1.0
PIPA §22-2 under-14 guardianreject_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 logpi_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 designationPlaceholder values still in privacy-screen.tsx:254-257.v1.0 — see pre-launch-checklist.md
PIPA §35-2 data portabilityexport-user-data edge function returns full JSON dump on demand.v1.0
정통망법 §50 marketing consentSeparate 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

CapabilityStatusGapTrack
Service-key rotation hygienePath 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 hardeningMigration 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 tablesAll 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 enforcementWave B fixed 7 known sites with .is('deleted_at', null). CLAUDE.md Components rule codifies the contract.v1.0
PII redaction in diagnostic payloadsredactPII helper (14 sensitive keys) wired into all 3 critical-error sinks. 26 unit tests cover the contract.v1.0
Idempotency keys on mutationsMigration 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

CapabilityStatusGapTrack
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 channelsRetired. 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 subscriptionsClock 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 visibilitycaptureCriticalError → MMKV crash log (survives process death) + Supabase client_errors + Sentry. Dev panel surfaces recent errors.v1.0
HINT propagation through error subclassescommit 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 gatingNo 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 isolationWave 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 supportWave 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

CapabilityStatusGapTrack
Sentry init + release tagginginitSentry captures release + dist from Updates.runtimeVersion + Updates.updateId. Crash reports bucketable by OTA group.v1.0
Sentry breadcrumb coverage on mutationsWave 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 logrecent_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 pathsWave 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 gateWave 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

CapabilityStatusGapTrack
Typecheck on all PR/pushyarn typecheck in CI workflow.v1.0
Test on all PR/pushyarn 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-mdValidates AGENTS.md table schema on every push.v1.0
lint:pi-schemaForces every new migration to either INSERT into purpose_catalog or carry a -- PI-EXEMPT: comment.v1.0
verify-no-stale-claimsPre-push hook validates commit message paths exist in the diff.v1.0
Branch protection on mainCI 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 disciplineCurrent 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

CapabilityStatusGapTrack
Hex-arch direction enforcement16 ArchUnitTS fitness tests + ESLint no-direct-supabase-in-presentation + no-supabase-auth-outside-adapter.v1.0
Domain entity Zod-schema coverageTYPE-3 closed. All 42+ entities export *Schema. Arch-test fs scan enforces new entities.v1.0
Mutation key contractrequire-mutation-key rule + ratchet at 0 waivers (cleared 2026-05-04).v1.0
Freshness contractrequire-freshness-contract rule + ratchet at 0 waivers.v1.0
Tamagui-only stylingno-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 safetyno-gorhom-bottom-sheet-in-shared (crashes web) + no-bare-scrollview-in-sheet (gesture-conflict guard).v1.0
Mock adapter cleanuppackages/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

CapabilityStatusGapTrack
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 buildProduction profile/channel exist; rebuild and smoke current 0.6.1 runtime before listing.v1.0
EAS iOS preview buildApple Developer enrollment + interactive credential setup pending; deferred to R4 Production Readiness with app-link/Kakao device smoke.v1.0
EAS iOS production buildDepends on iOS preview validation.v1.0
OTA preview channelyarn 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 listingBundle ID locked but listing assets not prepared.v1.0 (alongside iOS build)
Play Console listingApk uploadable but Data Safety / content rating questionnaires not done.v1.0

Ledger of audits

DateAuditFindingsResolution
2026-05-19 morning4-agent comprehensive (UX/error/security/perf)~60 findings, 3 CRITICAL + 5 worst HIGHWave A + Wave B + Wave C closed all CRITICAL + HIGH + most MEDIUM.
2026-05-19 afternoon3-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.

Markdown remains the source of truth. Run yarn docs:check before handoff.