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 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 ​

CapabilityStatusGapTrack
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 ​

CapabilityStatusGapTrack
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 ​

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 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 ​

CapabilityStatusGapTrack
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 ​

CapabilityStatusGapTrack
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 ​

CapabilityStatusGapTrack
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 ​

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 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 ​

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.