Skip to content

feat: Cuddler-first Growth OS (P18–P19) + loopback design language#1

Merged
oratis merged 17 commits into
mainfrom
feat/growth-cuddler-pilot
Jul 4, 2026
Merged

feat: Cuddler-first Growth OS (P18–P19) + loopback design language#1
oratis merged 17 commits into
mainfrom
feat/growth-cuddler-pilot

Conversation

@oratis

@oratis oratis commented Jul 4, 2026

Copy link
Copy Markdown
Owner

Extends Adex from an ad-execution platform into a growth operating system for its first customer, Cuddler (iOS-first AI-roleplay app running an initial $5,000 paid pilot). Also adopts the loopback design language.

What's inside

Strategy & decisions (docs/growth/)

  • 00-cuddler-first-redesign.md — three-layer redesign + Phase 18–21 roadmap (extends the shipped Phase 10–17 agent system)
  • 01-5k-pilot-plan.md — the $5K pilot's 6 key decisions, adjudicated from a pro/con debate (web-funnel as the SKAN-free measurement spine; sequential kill/scale gates; three-layer budget control)
  • 02-integration-contract.md — Cuddler-facing integration contract

P18 — measurement foundation

  • Schema: PromotedApp / ConversionEvent / CohortSnapshot / GrowthMetric + migration
  • kpi-canon.ts (single source of truth; eCAC* includes the video loss-leader), pilot-gates.ts (sequential kill/scale gates), channels.ts (taxonomy + UTM attribution)
  • RevenueCat ingest (bearer-auth, idempotent) + GA4 mapper + /api/ingest/events (HMAC) + growth-sync cohort cron
  • /growth dashboard: overview / channels (live gate status) / cohorts

P19 — organic attribution + paid readiness

  • Schema: CreatorPartnership / CreatorPost / AppReview + Campaign.promotedAppId
  • KOL natural-uplift attribution; App Store review RSS + sentiment/topic classification (LLM + rule-based fallback) + review-sync cron
  • budget-guard.ts (P6 pacing/cap/breach), campaign-objective.ts (objective resolver + SKAN-cap launch validator)
  • /growth creators + reviews pages

Design — adopts loopback's dark, terminal-native language with color-encoded agency (human=lime #a3e635 / agent=cyan #56c7f5). Spec: docs/design/00-loopback-design-language.md.

Verification

prisma validate + generate · 110 vitest · tsc --noEmit · eslint · next build — all green. Design confirmed on the live login page.

Not included (needs external creds / platform sandbox)

Adapter-side app-install API wiring (OUTCOME_APP_PROMOTION createCampaign), GA4/ASC/RevenueCat live pulls, ASA adapter (P20), Growth Agent tools (P21).

🤖 Generated with Claude Code

oratis and others added 17 commits July 4, 2026 19:36
WHY: Adex 的第一个真实客户 Cuddler 初期即投放 $5K,需要把 Adex 从
"投放 Agent 平台"扩展为服务其付费增长的"增长操作系统"。方案结合
HakkoAI 的增长基础设施能力与 Cuddler 的 GTM 与单位经济约束。

WHAT:
- 00-cuddler-first-redesign.md: 三层重构(数据底座/渠道执行/Agent 决策)
  + Phase 18–21 路线图,承接 docs/agent 的 Phase 10–17
- 01-5k-pilot-plan.md: $5K 首投的 6 个关键决策,正反方辩论 + 逐题裁决
  (web 漏斗为主测量臂、序贯 kill/scale 门、三层预算风控等)
- 02-integration-contract.md: 给 Cuddler 团队的对接契约(门禁/分工/时间线)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
WHY: P18 底座第一块,也是辩论结论的代码化。所有增长指标需单一口径
(避免 HakkoAI 的 strict/wide DAU 口径事故);$5K pilot 的 kill/scale
判据(P5 裁决)需可解释、可测。

WHAT:
- src/lib/growth/kpi-canon.ts: 指标单一事实来源。核心是 eCAC*(含视频
  loss-leader 补贴成本),修正裸 CPI 对真实 CAC 的系统性低估。realized
  vs projected LTV 严格分离;k-factor 带 measured/estimated 置信标记。
- src/lib/growth/pilot-gates.ts: 序贯 kill/scale 门(Gate A/B/C + 全局
  熔断),编码 payment_signal_gate 纪律——proxy 只能降/停,加码必须见
  真实付费信号。返回带理由的决策。
- 两个 .test.ts(vitest):25 断言,纯逻辑经 node type-strip 全绿。

Ref: docs/growth/01-5k-pilot-plan.md §P5

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
WHY: Cuddler pilot 的度量脊柱需要一套归一化的增长数据表——把 GA4/
RevenueCat/深链的转化事件、分渠道 cohort、非 cohort 增长指标落库,
供 /growth 看板、周报与 Agent perceive 读取。

WHAT(纯增量,不改现有表):
- PromotedApp: 被推广的 App(Cuddler iOS = 首行),app_install campaign
  经 Campaign.promotedAppId 指向
- ConversionEvent: 归一化漏斗事件,unique 键在 ingest 去重
- CohortSnapshot: 获客日 × 渠道 cohort,growth-sync cron 每日重算
- GrowthMetric: k-factor / 周分享数 / ASO 排名等非 cohort 指标
- Organization 加 4 个反向关系;全部 FK 从 Organization CASCADE
- 迁移手写(仓库无本地库约定),字段与 schema 交叉核对一致

NOTE: 本环境无 node_modules,未能在本地跑 prisma generate/validate;
生成的 client 由 Docker build(prisma generate)产出,迁移由 start.sh
的 migrate deploy 应用,CI 的 next build 会校验 schema。

Ref: docs/growth/00-cuddler-first-redesign.md §4

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
WHY: 渠道口径必须单点定义——ConversionEvent.channel、CohortSnapshot、
pilot 分臂、skan_maturity guardrail 都依赖同一套渠道分类,避免分叉。

WHAT:
- src/lib/growth/channels.ts: 规范渠道 id(web 漏斗/ASA/iOS-SKAN/KOL/
  organic 等),isPaidChannel / isSkanChannel(喂 pilot-gates 的 skanImmature),
  resolveChannel 按对接契约 adex_{arm} UTM 约定归因,带 deterministic/
  skan/inferred 置信标记。
- channels.test.ts(vitest):13 断言,node type-strip 全绿。

Ref: docs/growth/01-5k-pilot-plan.md §P2 · docs/growth/02-integration-contract.md §3.2

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…sionEvent

WHY: pilot 的 scale 判据只信真实付费信号(payment_signal_gate)。RevenueCat
是该信号来源,需一条鉴权 + 幂等的接收链路——直接规避 HakkoAI"支付回调无
鉴权无幂等"的 P0。

WHAT:
- events.ts: 规范事件名/来源 taxonomy + ConversionEventInput 形状(对齐
  Cuddler analytics_canon)
- ingest-auth.ts: verifyBearer(常量时间,RC 静态 Authorization 头)+
  verifyHmac(我方带签名事件推送,镜像 slack-signature)+ readBearer
- revenuecat.ts: mapRevenueCatEvent 纯映射(INITIAL_PURCHASE/RENEWAL/
  TRIAL/CHURN → 规范事件),防御性解析 unknown
- POST /api/ingest/revenuecat?org=<id>: 按 org 查 secret(PlatformAuth,env
  兜底)→ 常量时间校验 → createMany skipDuplicates 幂等落库
- 18 个新 vitest 断言(含 replay/篡改/长度不等边界);tsc + eslint 干净

Ref: docs/growth/00-cuddler-first-redesign.md §5 · 02-integration-contract.md §5

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
WHY: 分渠道 cohort(install→激活→D1/D7→trial→订阅→LTV)是 pilot 的度量
脊柱与 /growth 看板、周报、Agent perceive 的数据源。

WHAT:
- cohorts.ts: buildCohortSnapshots 纯聚合——用户归入其首个获客事件
  (install/signup)的(日×渠道)cohort;RC 无渠道的付费事件经 userKey
  归因到获客渠道;留存用 engagement 事件在 day+N 的出现近似;LTV 走
  kpi-canon.realizedLtv;CAC 可由传入 spend 计算否则 null
- POST /api/cron/growth-sync: 滚动 60 天,删窗口内旧快照 + 重插(幂等重算),
  X-Cron-Secret 鉴权,纳入现有 CronSecret 体系
- 8 个新 vitest 断言(含跨渠道同日分离、RC 归因、无 userKey 忽略、CAC)

Ref: docs/growth/00-cuddler-first-redesign.md §4.1 · §10 (P18)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
WHY: Cuddler 团队上线周即需在 Adex 内看到分渠道全漏斗,替代手工拼表
(对接契约 §2 的交付物)。

WHAT:
- GET /api/growth/overview: 把 org 的 CohortSnapshot 折叠成 funnel 总览 +
  分渠道明细,所有 rate 走 kpi-canon(看板/报告/Agent 同口径)
- /growth 页:funnel StatCards(installs/激活/D1/D7/订阅/收入)+ 分渠道
  cohort 表(激活/留存/订阅率/LTV/CAC)+ 未上线时的友好空状态
- sidebar 加"Growth"入口(🌱,Dashboard 之后)
- tsc + eslint + next build 全通过

Ref: docs/growth/00-cuddler-first-redesign.md §8

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ncy-encoded

WHY: 参考 loopback(loopback-pitch.pages.dev)重塑 Adex 设计风格。loopback
的招牌理念是"颜色即身份"——绿=人、青=机器;Adex 本身就是人+AI-agent 平台
(审批队列、guardrail、L0–L4 自治),这套理念几乎一对一契合。

WHAT:
- globals.css: 注册 loopback 令牌到 @theme(bg/panel/surface/line/ink/mut/
  signal/ai/ok/warn/bad/max + mono + 紧半径 5/8/11px);把既有 .dark 工具反转
  重新调成 loopback 精确配色;品牌蓝→signal(lime);live● 心跳动画
- dark-first:layout 预注入脚本 + theme-provider 默认深色(loopback 是深色家)
- 身份编码原则落地:sidebar 品牌/人类项=lime、agent-loop 项=cyan;
  status/severity badge executing=agent cyan;primary 按钮=lime 深字
- 主键 mono(StatCard 值、badge、live● 状态行 127.0.0.1 :: ready)
- Card 紧半径 + 面板/hairline
- docs/design/00-loopback-design-language.md:完整设计规范
- 验证:tsc + eslint + next build 全绿;登录页实机截图确认深色+lime 渲染

Ref: https://loopback-pitch.pages.dev/

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…status

WHAT:
- GET /api/growth/channels: per-channel roll-up + live pilot-gate decision
  (pilot-gates.evaluateChannel — same logic the Agent uses); eCAC*, SKAN flag
- GET /api/growth/cohorts: per-day×channel cohort rows (rates via kpi-canon)
- /growth/channels: channel table + GateBadge (scale/hold/halve/kill/freeze)
- /growth/cohorts: cohort table with lime→bad retention heat tint
- _ui.tsx: shared GrowthTabs (Overview/Channels/Cohorts/Creators/Reviews),
  channelLabel, pct, GateBadge — loopback mono/agency styling
- tsc + eslint + next build green

Ref: docs/growth/01-5k-pilot-plan.md §P5 · docs/growth/00-cuddler-first-redesign.md §8

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
WHAT:
- ga4.ts: mapGa4Event (Cuddler GA4 event names → canonical EventName, channel
  from UTM) + thin runGa4Report REST client (token via ADC, like storage.ts)
- ingest-parse.ts: parseIncomingEvents — validate/resolve canonical events,
  drops unknown names/sources (never trusts input)
- POST /api/ingest/events?org=: HMAC-authed batch push (backup channel when not
  pulling GA4), idempotent via ConversionEvent unique key
- 12 new vitest assertions (80 total); tsc + eslint clean

Ref: docs/growth/00-cuddler-first-redesign.md §5

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
WHAT (additive, no existing-table changes):
- CreatorPartnership + CreatorPost: KOL cooperation台账 + per-post natural-uplift
  attribution fields (baseline/uplift installs, effectiveCpi) — hakko-kol-agent method
- AppReview: store review + LLM classification (sentiment/topics/priority);
  id = source:review_id hash → idempotent on re-ingest
- Organization back-relations; FKs cascade; migration hand-authored + field-
  cross-checked; prisma validate + generate pass

Ref: docs/growth/00-cuddler-first-redesign.md §4.1

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
attributePost: installs above the pre-publish baseline over an attribution
window → upliftInstalls + effective CPI (hakko-kol-agent method). Quasi-
experiment, documented as directional. 3 vitest cases.

Ref: docs/growth/00-cuddler-first-redesign.md §1.3

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…tion

WHAT:
- reviews.ts: parseAppStoreReviews (public RSS JSON, no auth) + rule-based
  fallbackClassify (sentiment/topics/priority from rating+keywords, ported
  Feedback_agent taxonomy) + LLM classifyReviews (completeJSON, falls back on
  failure) + fetchAppStoreReviews
- POST /api/cron/review-sync: pull per iOS PromotedApp × [us,gb], classify,
  upsert AppReview (idempotent by id), count new P0s
- 10 new vitest assertions (90 total); tsc + eslint clean

Ref: docs/growth/00-cuddler-first-redesign.md §1.3, §10

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ict)

Pure. evaluatePilotBudget (global $5K pacing: warn 80% / auto-pause 95%),
evaluateChannelBreach (one-directional auto-pause only on egregious breach;
learning-phase + SKAN-immature channels protected from stale-data pauses),
tierCacCeiling/withinCacCeiling (eCAC* ≤ 5× first-month net). 15 vitest cases.

Ref: docs/growth/01-5k-pilot-plan.md §P6

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
WHAT:
- Campaign.promotedAppId field + migration (app_install/web_conversion campaigns
  point at a PromotedApp)
- campaign-objective.ts (pure): resolvePlatformObjective (neutral → Meta/TikTok/
  Google objective) + validateLaunch — app_install needs bundleId/storeId,
  enforces SKAN iOS campaign cap (Meta 9 / TikTok 11), web_conversion needs a
  destination domain. The launch route calls this before the adapter.
- 12 vitest cases; prisma validate/generate + tsc + eslint clean

NOTE: adapter-side API wiring (actual OUTCOME_APP_PROMOTION createCampaign)
is the remaining integration step — needs platform sandbox creds.

Ref: docs/growth/00-cuddler-first-redesign.md §5.2

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
WHAT:
- GET /api/growth/creators: KOL partnerships + posts + blended effective CPI
  (cost / total uplift, via kpi-canon)
- GET /api/growth/reviews: recent classified reviews + sentiment/priority filter
  + P0/negative counts
- /growth/creators: partnership cards with per-post uplift table (mono, agency
  cyan links)
- /growth/reviews: review stream with sentiment/priority/topic chips + filters
- loopback-styled; tsc + eslint + next build clean

Ref: docs/growth/00-cuddler-first-redesign.md §8

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
AgentPulse heartbeat strip (lb-live + mono `127.0.0.1 :: ready`) on the growth
overview; mono/tabular-nums across the channel table. Agent surfaces (decisions/
approvals) already inherit the agency lens via the restyled status/severity
badges (executing=cyan) + token cascade.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@oratis oratis merged commit d26cd0a into main Jul 4, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant