feat(paycraft): track 84 migrations + close anon SECURITY DEFINER surface · native billing · full paywall surface · locale-correct pricing - #157
Draft
therajanmaurya wants to merge 31 commits into
Conversation
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: trueThanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…om/mobilebytelabs/paycraft/presentation/ProviderBottomSheet.kt cmp-paycraft/src/commonMain/kotlin/com/mobilebytelabs/paycraft/presentation/templates/BrandedStackTemplate.kt
…ytelabs/paycraft/billing/PlayBillingNativeClient.android.kt cmp-paycraft/src/commonMain/kotlin/com/mobilebytelabs/paycraft/billing/NativeBillingClient.kt cmp-paycraft/src/commonMain/kotlin/com/mobilebytelabs/paycraft/core/PayCraftBillingManager.kt
…telabs/paycraft/core/PayCraftBillingManager.kt cmp-paycraft/src/commonMain/kotlin/com/mobilebytelabs/paycraft/network/PayCraftService.kt cmp-paycraft/src/iosMain/kotlin/com/mobilebytelabs/paycraft/billing/PlatformNativeBillingClient.ios.kt
…telabs/paycraft/PayCraft.kt cmp-paycraft/src/commonMain/kotlin/com/mobilebytelabs/paycraft/core/PayCraftBillingManager.kt cmp-paycraft/src/commonMain/kotlin/com/mobilebytelabs/paycraft/di/PayCraftModule.kt
…ytelabs/paycraft/billing/PlayBillingNativeClient.android.kt cmp-paycraft/src/commonMain/kotlin/com/mobilebytelabs/paycraft/billing/NativeBillingClient.kt cmp-paycraft/src/commonMain/kotlin/com/mobilebytelabs/paycraft/core/PayCraftBillingManager.kt
…ytelabs/paycraft/billing/PlayBillingNativeClient.android.kt gradle/libs.versions.toml
…androidMain/kotlin/com/mobilebytelabs/paycraft/billing/PlayBillingNativeClient.android.kt gradle/libs.versions.toml
…ytelabs/paycraft/PayCraftPlatform.android.kt cmp-paycraft/src/androidMain/kotlin/com/mobilebytelabs/paycraft/billing/PlayBillingNativeClient.android.kt cmp-paycraft/src/commonMain/kotlin/com/mobilebytelabs/paycraft/debug/PayCraftLogger.kt
… gradle/libs.versions.toml
…ample-app/src/androidTest/kotlin/com/mobilebytelabs/paycraft/sample/BasePayCraftUiTest.kt
…ample-app/src/androidInstrumentedTest/kotlin/com/mobilebytelabs/paycraft/sample/BasePayCraftUiTest.kt
…gin-template/build.gradle.kts
…AUNCH_RUNBOOK.md plugin-template/build.gradle.kts
…dea-layer/state/AGENT_AWARENESS.jsonl
…/[id]/pricing/page.tsx dashboard/app/api/products/[id]/route.ts
…packages.sql supabase/migrations/090_lifetime_entitlement_semantics.sql
…m/mobilebytelabs/paycraft/billing/PayCraftStoreKit.kt dashboard/lib/customer-geo.ts
…/com/mobilebytelabs/paycraft/PayCraft.kt cmp-paycraft/src/commonMain/kotlin/com/mobilebytelabs/paycraft/config/ConfigClient.kt
…eResources/values/strings.xml cmp-paycraft/src/commonMain/kotlin/com/mobilebytelabs/paycraft/PayCraft.kt
…T.yaml idea-layer/state/PAYCRAFT_PHASE4_VERDICTS.yaml
…-shadow.ts supabase/functions/config/index.ts
…eResources/values/strings.xml cmp-paycraft/src/commonMain/kotlin/com/mobilebytelabs/paycraft/config/ConfigResult.kt
…abs/paycraft/config/BundledFallback.ios.kt
…androidMain/kotlin/com/mobilebytelabs/paycraft/PayCraftPlatform.android.kt cmp-paycraft/src/androidMain/kotlin/com/mobilebytelabs/paycraft/billing/PlayBillingNativeClient.android.kt
…se/functions/config/index.ts supabase/migrations/099_restore_upsert_natural_key.sql
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Production-hardens the PayCraft SDK and closes the anon-reachable surface on its Postgres. Brings
84 previously-untracked migrations under version control (a blanket
supabase/migrations/*ignorehad been silently dropping schema from deploy), adds the native billing lane and the four-state
paywall surface, and lands five security migrations plus a locale-price fix — both driven by
defects found on a real device and in a live database, not by code review.
Changes
Schema now tracked (84 migrations)
.gitignoreblanket-ignore + per-file-negation pattern is removed; every migration isversioned. This is the defect that left 73 migrations out of CI.
094revoke anon on 14 unguarded SECURITY DEFINER writers ·095tenant guardon the 3 still reachable by a logged-in user ·
096lock the crypto primitives + a missedupgrade_tenant_planoverload ·097close the remaining anon surface ·098drop the OTP gate.Native billing — Play Billing v9 (
PlayBillingNativeClient.android.kt) and a StoreKit 2 bridge,behind
NativeBillingClient/NativeOffer, withOfferSelectionTest+PurchaseLoopTest.Paywall surface — every
BillingStatearm renders:ConfigResilienceUi,EmptyProductsContent,DeviceConflictContent,OwnershipVerifiedContent,BuiltInPaywall,PayCraftSurfaceMode, plus a bundledpaycraft-fallback.jsonfor the offline/cold-start path.Locale-correct pricing — all 8 render sites now resolve through the precedence rule
(native store price > cloud per-locale price > base price) instead of formatting the base price.
OTP gate removed — SDK actions/handlers/UI, 4 test tags, 4 strings, the
otp-send-hookEdgeFunction, and the DB objects. Device conflicts resolve via OAuth or support.
Dependency holds — Supabase held at 3.1.4 and Store5 at
5.1.0-alpha10; Kermit removed infavour of an internal
PlatformLog.AI SoT corpus —
paycraft-instructions/(BILLING_STATE_SEMANTICS,FAILURE_MODES,KEY_TIERING,PAYWALL_CUSTOMIZATION,MANIFEST.yaml).Three defects carried out of the epic, now closed
099restoresON CONFLICT (tenant_id, sku)totenant_products_upsert. This was a REGRESSION:058 had already fixed it, and 087 re-declared the function "copied VERBATIM from 084" — carrying
084's
ON CONFLICT (id)back in and silently undoing it. A dashboard PATCH sending a fresh UUIDfor an existing SKU therefore raised on the
(tenant_id, sku)unique constraint instead ofupdating.
idis deliberately omitted from the UPDATE arm so a SKU match keeps its primary keyand existing references stay valid.
app/api/products/[id]/route.tsnow selects the full row and merges it beneath the patch. PATCHis partial by definition but the RPC takes a full row, so a payload changing only
active— theenable/disable toggle — arrived with no
skuand hit a NOT NULL violation surfaced as a 500. Thesame narrow select was the audit log's before-image, so
product.updatedentries could not showwhat changed; they now carry the whole row.
supabase/functions/config/index.tsfails loudly onpaywallRes,providersResandtenantResinstead of degrading them to defaults.
providersResmattered most: an outage became zeroproviders, rendering a paywall with no way to pay that looks like tenant misconfiguration.
tenantResseparates a missing row (PGRST116 → 404) from a transport failure (→ 500).Notable files
supabase/migrations/096— an unguarded decryption oracle.decrypt_provider_key(bytea)loadsthe server passphrase and returns
pgp_sym_decrypt(...)with no authorisation, and anon heldEXECUTE. It is the primitive that
tenant_providers_decrypt_keyandtenant_stripe_connect_decryptare built on — both correctly guarded — so the guards could be walked around by calling the layer
beneath them. Static review could not see it: the scan classified functions by whether they write
rows, and this one only reads. It surfaced only when the live catalog was asked what anon can
execute (14 by file inspection; 61 by probe).
supabase/migrations/097— a guard that was inert against the caller it had to stop.tenant_pricing_bulk_upsertusedIF auth.uid() IS NOT NULL AND NOT EXISTS(tenant_admins…). Thatprefix exists so
service_role(NULL uid) passes — but anon also has a NULL uid, so thecondition short-circuited and the write proceeded. Reads as guarded, was wide open.
gradle/libs.versions.toml— the holds are load-bearing. Publishing at the previous dependencyset crashed every consumer at launch: PayCraft had moved above consumer pins, inverting Gradle's
resolution. Store5
alpha11was the sole requester of Kermit 2.1.0 in a consumer's graph — everyother requester asked for 2.0.x — so removing PayCraft's direct Kermit dependency did not fix it;
holding Store5 did.
ui/ProductList.kt:396— JVM-green, Android-fatal.Regex("\{[A-Za-z_]+}")compiles on the JVMand throws
PatternSyntaxExceptionon Android's ICU engine. As a top-levelvalthe throw lands in<clinit>, so every paywall render died withExceptionInInitializerError. 385 unit tests and 14Roborazzi goldens passed throughout — they all run on the JVM.
Verification
382 tests pass · 14/14 Roborazzi goldens unchanged ·
deno checkclean on the config function ·tsc --noEmitexit 0 on the dashboard · G-PAYCRAFT-DEPLOY passes (it caught 099 as untracked beforethis was called done) · migrations 094–098 applied and privilege-probed
against a local Postgres (crypto primitives closed, both
upgrade_tenant_planoverloads closed,9/9 SDK RPCs still anon-executable) · paywall device-verified on CPH2423 / Android 15 rendering a
single currency end-to-end.
Not verified: migrations 094–099 are unapplied in PRODUCTION — every privilege claim above was
measured against a local Postgres. Migration 099's conflict-target change has not been exercised
against real data, and no iOS storefront capture exists yet (AC-13).