From 14674b1f5ac51f544ee6d239bedf04b544bc7d93 Mon Sep 17 00:00:00 2001 From: engineer Date: Fri, 24 Jul 2026 07:04:52 -0700 Subject: [PATCH 1/3] docs: data safety audit for Email Address (Play rejection versionCode 142) --- docs/data-safety-audit.md | 87 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 docs/data-safety-audit.md diff --git a/docs/data-safety-audit.md b/docs/data-safety-audit.md new file mode 100644 index 00000000..f0b67b00 --- /dev/null +++ b/docs/data-safety-audit.md @@ -0,0 +1,87 @@ +# Data Safety Audit — Email Address (Play rejection versionCode 142) + +App: **OpenCode: AI Coding Agent** · package `cc.agentlabs.opencode` +Rejection: Google Play rejected update **versionCode 142** for *"Invalid Data safety form"* — an **Email Address** transmitted off-device that is not declared in the Data safety form. + +--- + +## 1. Which commit == versionCode 142 + +**versionCode is assigned remotely by EAS, not committed in git.** `eas.json` sets: + +```json +"cli": { "appVersionSource": "remote" } +``` + +With `appVersionSource: "remote"`, Expo/EAS auto-increments and owns the Android `versionCode` on its build servers. The values committed in the tree (`android/app/build.gradle` → `versionCode 34`, `app.json` → `versionCode 37`) are **stale and ignored for production app-bundle builds**. `git log`/`git tag` top out at versionCode 39 (tag `v0.4.12`, SHA `3ac7eb7`); **142 never appears anywhere in git history**, tags, `app.json`, `app.config.*`, or `eas.json`. EAS CLI is not installed on this machine, so the exact build→SHA map could not be pulled from `eas build:list`. + +**Consequence for this audit:** email egress is a *property of the app source*, not of a specific `versionCode`. It does not vary between builds around 142. The audit was therefore performed at the authoritative current source: **`origin/main` @ `6c103ac`** (fetched fresh). This is the code that produces the production bundle and is the correct target for the Data-safety declaration. + +Relevant already-merged history: +- `d6e24e9` — *fix(compliance): disclose email collection in Play Data Safety* (PR #146, closes #143). This is the prior fix for **this same rejection** (Play rejected `cc.agentlabs.opencode` on 2026-07-22 for undisclosed Email Address). It updated the listing/privacy docs to declare the waitlist email → Brevo. versionCode 142 is the build that triggered/carried this rejection cycle. + +> Audit revision: **`origin/main` @ 6c103ac** (read via `git show :`; working tree kept clean). + +--- + +## 2. Email egress paths found + +Grepped the entire app+src tree at `origin/main` for `email|setUser|identify|posthog|amplitude|analytics|sentry|revenuecat|Purchases.logIn|segment` and read every hit. **Exactly one** path sends an email off-device. + +| Source (file:line / SDK) | Data type | Where it goes | Purpose | Optional or Required | +|---|---|---|---|---| +| `app/connection/add.tsx:217` → `src/lib/waitlist.ts:submitWaitlistSignup` (POST `https://opencode.agentlabs.cc/api/beta-signup`) | Email Address | First-party beta-signup API → **Brevo** (email marketing/CRM) | "OpenCode Connect" waitlist signup (account/contact for a future hosted product) | **Optional** — user only types an email into a dismissible waitlist card and taps submit | +| `app/connection/add.tsx:227` → `src/lib/waitlist.ts:buildWaitlistMailtoUrl` (mailto fallback) | Email Address | `mailto:support@agentlabs.cc` (user's own mail client) | Fallback for the same waitlist when the API is unreachable | Optional — user-initiated send from their own mail app; not a developer network collection | + +### Paths that do NOT transmit email (ruled out) +- **Sentry** (`src/lib/sentry.ts`): `sendDefaultPii: false`; no `Sentry.setUser`/email anywhere; `beforeSend` → `scrubEvent` and `redactObject` strip `url/host/hostname/authorization/token/password/username/...`; console breadcrumbs dropped. **No email, no PII.** Gated behind first-launch consent (`TelemetryConsentModal`). +- **PostHog** (`src/lib/analytics.ts`): self-instantiated, no autocapture, **no `identify()`/`email`/`setUser`**; random anonymous distinct ID + coarse event props only. Gated behind the same consent. +- **Connection `username`** (`src/lib/auth.ts`): a Basic-auth username for the user's **own** self-hosted OpenCode server (defaults to `"opencode"`). Sent only to the host the user typed, never to a developer/third-party endpoint. Not an email, not developer-collected PII. +- **No** RevenueCat / `Purchases.logIn`, **no** Firebase Auth / `signInWith*`, **no** Amplitude/Segment, **no** other `fetch`/POST body containing an email. + +**What Play most likely detected on 142:** the waitlist `POST … {email} → Brevo` (the only real off-device email), which was undeclared at the time. (Not Sentry/PostHog auto-capture — those carry no email.) + +--- + +## 3. Exact Play Console Data safety answers — Email Address + +Data type: **Personal info → Email address** + +- **Collected?** **Yes** (the app sends it off-device to the developer's server / Brevo). +- **Shared?** **Yes** — shared with **Brevo** (Sendinblue), a third-party CRM/email processor. (Play "shared" = transferred to a third party.) +- **Processed ephemerally?** **No** — Brevo persists it on a contact list. +- **Required or optional?** **Optional** — the user chooses to enter an email in the waitlist card; the app is fully usable without it. +- **Purposes** (check exactly these Play boxes): + - ✅ **Account management** (waitlist / future-product account) + - ✅ **Developer communications** (product/waitlist updates via Brevo) + - ⬜ App functionality — *not* required for the app to work + - ⬜ Analytics · ⬜ Fraud prevention/security/compliance · ⬜ Advertising/marketing · ⬜ Personalization + - *(If Brevo is used to send promotional emails, additionally check **Advertising or marketing**. Only check it if that is actually true.)* +- **Encrypted in transit?** **Yes** — HTTPS (`https://opencode.agentlabs.cc/...`). +- **Deletion mechanism?** **Yes** — user can request deletion by emailing `support@agentlabs.cc`; must be documented in the privacy policy (already added in §3c / third-party-services of `distribution/privacy-policy.md` via PR #146). + +--- + +## 4. Recommendation — declare vs remove (per source) + +| Source | Recommendation | Why | +|---|---|---| +| **Waitlist → Brevo** (`waitlist.ts`) | **DECLARE** (Email = Collected + Shared/Brevo, Optional, Account management + Developer communications) | This is an intentional, user-initiated, optional feature with a real business purpose. Declaring it is correct and low-risk. PR #146 already wrote the matching privacy-policy + listing copy; the fix is simply to make the **Play Console Data safety form** match §3 above and resubmit. | +| Sentry / PostHog / auth username | **No declaration needed for Email** | No email leaves the device via these; already handled correctly. | + +**Headline recommendation: DECLARE the email (do not remove the waitlist).** The single real egress is an optional, opt-in waitlist that forwards email to Brevo for account/communications — a legitimate, disclosed use. Removing it would delete a working growth feature for no compliance benefit. Fill the Play Console Data safety form for **Email Address** exactly as in §3 (Collected: yes; Shared: yes → Brevo; Optional; Purposes: Account management + Developer communications; Encrypted in transit: yes; deletion via support@agentlabs.cc) and resubmit. + +*Optional hardening (future PR, not made here):* if the waitlist is not a priority, an alternative is to **remove** the API POST and keep only the `mailto:` fallback (`buildWaitlistMailtoUrl`) — a user-initiated send from their own mail client is not a developer network collection, which would let you answer Email = **Not collected**. This is a product decision, not a compliance requirement; declaring is the cleaner, recommended path. + +--- + +## Appendix — how the audit was run + +``` +git fetch origin --tags --prune +git grep -n -i -E "email|setUser|identify|posthog|amplitude|analytics|sentry|revenuecat|Purchases.logIn|segment" origin/main -- 'src/**' 'app/**' '*.ts' '*.tsx' '*.js' +git show origin/main:src/lib/waitlist.ts # confirmed POST → opencode.agentlabs.cc/api/beta-signup → Brevo +git show origin/main:src/lib/sentry.ts # sendDefaultPii:false, scrubEvent, no setUser/email +git show origin/main:src/lib/analytics.ts # PostHog, no identify/email, anonymous ID only +git show origin/main:src/lib/auth.ts # username = self-hosted server Basic-auth, not email +``` From bf56cba4c5691b83ff5c4bd0bbc85fe7d4048ae9 Mon Sep 17 00:00:00 2001 From: Den Date: Sat, 25 Jul 2026 09:34:30 -0700 Subject: [PATCH 2/3] growth: qualify 8 founding-member leads + draft outreach; park Stripe test key as founder-gated Leads qualified from real repo engagement (issues, PRs, forks, stars), ranked by purchase intent, each with contact channel and fit rationale. Outreach message drafted with per-lead hooks and send rules. Blocked on: Bitwarden Secure Note STRIPE_TEST_SECRET_KEY (folder opencode-mobile). Vault only has OpenClawBot - STRIPE_SECRET_KEY which is sk_live_ and off-limits. --- distribution/founding-members.md | 123 +++++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 distribution/founding-members.md diff --git a/distribution/founding-members.md b/distribution/founding-members.md new file mode 100644 index 00000000..bd9dcd53 --- /dev/null +++ b/distribution/founding-members.md @@ -0,0 +1,123 @@ +# Founding Members — first paid cohort + +**Status:** leads qualified, outreach drafted, **send BLOCKED on Stripe test key** (see § Blocker). +**Goal:** OpenCode Mobile's first dollar. Current MRR: **$0**. No Stripe product exists yet. +**Date qualified:** 2026-07-25 + +--- + +## Blocker (one-line ask for Den) + +> **Den — add a Bitwarden Secure Note named `STRIPE_TEST_SECRET_KEY` (folder `opencode-mobile`) containing the `sk_test_...` key from https://dashboard.stripe.com/test/apikeys; the vault only has `OpenClawBot - STRIPE_SECRET_KEY` which is `sk_live_` and is off-limits.** + +Why it's founder-gated: creating the test key requires an interactive login to the Stripe Dashboard on the +VIBE TECHNOLOGIES account (`vibeteaichnologies@gmail.com`). Stripe's login is anti-automation and the +CDP-driven browser session could not complete it. + +Once that vault entry exists, everything below is unblocked and scriptable — no further founder input needed. + +| Item | State | +|---|---| +| `STRIPE_TEST_SECRET_KEY` in Bitwarden (`opencode-mobile`) | **MISSING — founder-gated** | +| `OpenClawBot - STRIPE_SECRET_KEY` (`sk_live_`) | exists — **must not be used**, live mode | +| Stripe products (Free / $4.99 / $9.99) | not created — needs test key | +| Test checkout link | not created — needs products | +| Outreach message | **ready** (below) | +| Lead list | **ready** (below) | + +--- + +## The 8 leads + +Qualified from real repository engagement (issues filed, code contributed, forks, stars) — not scraped. +**None publish a public email**, so the contact channel is a GitHub `@mention` on the specific thread +they already opened. That is warmer than cold email anyway: they get a notification tied to their own words. + +Ranked by purchase intent. + +| # | Handle | Name / Locale | Contact channel | Evidence of intent | Fit rationale | Tier to pitch | +|---|---|---|---|---|---|---| +| 1 | `PylotLight` | "Light" — DevOps/Go dev | `@PylotLight` on **issue #134** (open) | Asked: *"Any chance at adding support for public hosted instances behind oidc/openid auth?"* | **Asked for the hosted product by name.** Wants a public hosted instance with SSO — that IS OpenCode Connect. DevOps background = pays for infra to not run infra. | **$9.99 Team** | +| 2 | `cloph-dsp` | Pedro Castro — Lisbon, PT | `@cloph-dsp` on **issue #53** | 4 issues (#53, #55, #56, #57), 1 PR (#54), fork, star | Heaviest external contributor. Reported the keyboard bug **and wrote a fix**. His PR was closed but the bug shipped fixed in #148 — owed a "you were right, it shipped" note. Deep daily user. | **$4.99 Pro** | +| 3 | `zFitness` | Shenzhen, CN — works at **@shopee** | `@zFitness` on **issue #150** | 2 issues (#147 keyboard, #150 refresh), fork **2026-07-24** | Active *this week*. Employed at a large tech co (Shopee) — corporate card, low price sensitivity. Blog: `zfitness.me`. | **$9.99 Team** | +| 4 | `2740653660` | "yyxxd" | `@2740653660` on **PR #68** | Shipped 2 features: #68 i18n + Simplified Chinese, #67 group sessions by project dir | Contributed *code*, not complaints. Highest goodwill in the pool. Ideal early advocate + zh-Hans channel. | **$4.99 Pro** (offer free year for contribution) | +| 5 | `e-lie` | Elie Gavoty | `@e-lie` via fork / GitHub profile | Forked **2026-07-25** — freshest signal in the pool | Forked within 24h of qualification — actively evaluating right now. Strike while hot. | **$4.99 Pro** | +| 6 | `SDBdevelopment` | — | `@SDBdevelopment` via fork | Forked 2026-07-18 | Handle implies a dev shop/agency → multi-seat potential. | **$9.99 Team** | +| 7 | `Med-amine0` | — | `@Med-amine0` via fork | Forked 2026-06-12 | Sustained interest (forked >6wk ago, repo still forked). Lower urgency. | **$4.99 Pro** | +| 8 | `mathysIN` | "mathys" — France | `@mathysIN` via fork | Forked 2026-05-20 — earliest external fork | Earliest believer, pre-dates most stars. Nostalgia/OG angle: "you found this before anyone." | **$4.99 Pro** | + +**Reserve pool** (if any of the 8 go cold): stargazers with recent dates — +`Vitalii953`, `wsuff`, `FANATFANATA`, `2gn`, `song19931218`, `iAdanos`, `kiyou`, `heyalucardvania-cmyk`. + +--- + +## Outreach message — ready to send + +Post as a **comment on the person's own thread** (or a GitHub issue `@mention` for fork-only leads). +Personalize the `[HOOK]` line per the table above; everything else is fixed. + +### Template + +> **[HOOK]** +> +> I'm the maintainer of OpenCode Mobile. I'm opening a small **Founding Member** group — 8 people — +> before I turn on billing, and you're one of them. +> +> Here's the deal, plainly: +> +> - **OpenCode Connect** is the hosted version. No server to run, no Tailscale, no port forwarding — you +> open the app and it works. It's what people ask for most, and it's the thing that costs me money to run. +> - Founding Members get **50% off for life** on whatever tier they pick, and a direct line to me for +> feature requests. Your issues skip the queue. Permanently. +> - Free tier stays free, forever. Self-hosting stays free, forever. This does not paywall anything that +> works today. +> +> Planned pricing (Founding Member price in bold): +> +> | | Free | Pro | Team | +> |---|---|---|---| +> | Price | $0 | ~~$4.99~~ **$2.49**/mo | ~~$9.99~~ **$4.99**/mo | +> | Connect to your own server | ✅ | ✅ | ✅ | +> | Hosted OpenCode Connect | — | ✅ 1 workspace | ✅ 5 workspaces | +> | SSO / OIDC | — | — | ✅ | +> | Priority support | — | ✅ | ✅ | +> +> I'm not asking for money in this message. I'm asking one question: **would you pay for this, and at +> which tier?** A one-word answer is a complete answer. If it's "no," tell me why — that's just as useful +> and I'll take it on the chin. +> +> — Den + +### Per-lead `[HOOK]` lines + +| Lead | `[HOOK]` | +|---|---| +| `PylotLight` | Hey — you asked in #134 about hosted instances behind OIDC. I'm building exactly that, and I want you in the first group. | +| `cloph-dsp` | Pedro — you reported the keyboard bug in #53 *and* sent a fix in #54. I closed your PR and then shipped the same fix myself in #148. That was a bad look on my part; you were right. Thank you for four issues that all made the app better. | +| `zFitness` | 你好 — you filed #147 and #150 this week and forked the repo. You're one of the most active users I have right now. | +| `2740653660` | You shipped Simplified Chinese support (#68) and project-directory grouping (#67) into this app. I owe you one — so this offer comes with a free year on me, no strings. | +| `e-lie` | Elie — you forked this yesterday, so you're evaluating it right now. Let me save you some time. | +| `SDBdevelopment` | You forked OpenCode Mobile last week — if you're deploying this for a team, I want to talk before I set pricing. | +| `Med-amine0` | You forked this back in June and it's still on your account. Curious what you're using it for. | +| `mathysIN` | mathys — you forked this in May. You were the first person outside my own account to do that. | + +### Sending rules + +- **Do not send until `STRIPE_TEST_SECRET_KEY` exists** — a "would you pay?" message with no way to + actually pay burns the warmest leads in the list. One shot each. +- Send in **two waves**: leads 1–4 (high intent) first, measure reply rate, then 5–8. +- Stop/continue rule: **if fewer than 2 of the first 4 reply within 72h, stop and rewrite the offer** — + the problem is the pitch, not the audience. +- Log every reply in this file under a `## Replies` section with date + tier answer. + +--- + +## Success criteria + +| Metric | Baseline (verified) | Target | Deadline | +|---|---|---|---| +| MRR | **$0** | > $0 (first dollar) | 14 days after Stripe unblock | +| Founding Member replies | 0 | ≥ 4 of 8 | 7 days after send | +| Stated willingness to pay | unknown | ≥ 2 say Pro or Team | 7 days after send | + +Do not report a metric here as moved without a Stripe dashboard or GitHub thread link proving it. From 537e18db1e44a1585be9515d1684c713a8dfc064 Mon Sep 17 00:00:00 2001 From: Den Date: Sat, 25 Jul 2026 09:34:42 -0700 Subject: [PATCH 3/3] docs(memory): log founding-member qualification and Stripe blocker --- .agents/memory/2026-07-24.md | 3 +++ .agents/memory/2026-07-25.md | 9 +++++++++ 2 files changed, 12 insertions(+) create mode 100644 .agents/memory/2026-07-24.md create mode 100644 .agents/memory/2026-07-25.md diff --git a/.agents/memory/2026-07-24.md b/.agents/memory/2026-07-24.md new file mode 100644 index 00000000..5b90aaf3 --- /dev/null +++ b/.agents/memory/2026-07-24.md @@ -0,0 +1,3 @@ +# 2026-07-24 + +- Scheduler check: exact prompt already configured as `opencode-mobile-owner-hourly-v4` (`0 * * * *`, 50-minute timeout, `commander`, `github-copilot/gpt-5.6-sol`). Job was running at 08:00 PDT. No duplicate or update created. diff --git a/.agents/memory/2026-07-25.md b/.agents/memory/2026-07-25.md new file mode 100644 index 00000000..5dbe9704 --- /dev/null +++ b/.agents/memory/2026-07-25.md @@ -0,0 +1,9 @@ + +## 2026-07-25 — Founding-member cohort qualified; Stripe test key founder-gated + +- **Stripe BLOCKED (founder-gated):** vault has only `OpenClawBot - STRIPE_SECRET_KEY` = `sk_live_` (107 chars). Test mode requires a new Bitwarden Secure Note `STRIPE_TEST_SECRET_KEY` in folder `opencode-mobile`. Stripe dashboard login is anti-automation; CDP browser could not complete it. Did NOT touch the live key. +- **8 leads qualified** from real engagement, not scraping: PylotLight (#134 asked for OIDC hosted — strongest paid signal), cloph-dsp (4 issues + PR #54 closed-but-shipped-as-#148), zFitness (2 issues + fork this week, works @shopee), 2740653660 (shipped i18n #68 + #67), e-lie/SDBdevelopment/Med-amine0/mathysIN (forks). +- **No public emails on any of the 8** → contact channel is GitHub @mention on their own thread. Warmer than cold email. +- Artifact: `distribution/founding-members.md`, branch `growth/founding-members`, commit `bf56cba`. +- **Send rule recorded:** do not send outreach until Stripe test key exists — "would you pay?" with no way to pay burns the warmest leads. +- Baseline stays honest: MRR $0, replies 0, willingness-to-pay unknown.