Overview
Deployment (first environment, 2026-10-17)
The first Cloudflare environment: Workers, D1, KV, R2, queues and secrets.
The production domain is clarisay.com (Cloudflare Registrar, zone in the same account). All three Workers serve it
through Workers Custom Domains (routes with custom_domain: true in each wrangler.jsonc;
wrangler deploy creates the DNS records and certificates). Their workers.dev URLs stay enabled: TestFlight builds up
to 21 call the backend's workers.dev URL. Everything below was created with wrangler from the respective track
directory; ids live in each wrangler.jsonc. The dashboard's mock and integration environments set routes: [] so
they never claim the production hostname (wrangler environments inherit routes).
| Project | URL | Also reachable at | Worker |
|---|---|---|---|
| Backend API | https://api.clarisay.com (/health, /v1/...) |
https://shuke-api.dou-tech.workers.dev | shuke-api |
| Operator dashboard | https://dash.clarisay.com | https://shuke-dashboard.dou-tech.workers.dev | shuke-dashboard |
| Landing | https://clarisay.com (/, /zh), https://www.clarisay.com |
https://shuke-landing.dou-tech.workers.dev | shuke-landing |
ios/Config/Release.xcconfig (API_BASE_URL) points at https://api.clarisay.com/v1 for the next
TestFlight build.
dashboard/wrangler.jsonc BACKEND_URL names https://api.clarisay.com/v1; the request still travels over the service
binding.
Resources
| Resource | Name / id |
|---|---|
| D1 | shuke — 5208d8af-6b45-4abe-bf6d-4b40743f725a (migrations 0001–0002 applied, seeds/ loaded) |
| KV | CONFIG aec399b3372f4f9b81fb8cd5ece9c9a5, IDEMPOTENCY 0d739a118cca450790c0b93f55d4ee8e, landing WAITLIST c4ad99d059f544ac8a410b874892bcd4 |
| R2 | shuke-audio |
| Queues | shuke-pipeline (producer + consumer), shuke-pipeline-dlq |
| Vectorize | shuke-content (1024 dims, cosine, metadata index language) — bound as CONTENT_INDEX |
| Crons | 0 18 * * *, 30 18 * * *, 5 * * * *, 0 19 * * * |
Secrets
Backend: JWT_SECRET, ADMIN_KEY (set with wrangler secret put). The generated values are in
~/.shuke/production-secrets.env on the machine that deployed (mode 600, not in git). Rotate with
openssl rand -hex 32 | pnpm wrangler secret put JWT_SECRET (existing access tokens expire within an hour).
Dashboard: no secrets. Cloudflare Access (below) authenticates operators; the proxy forwards the Access assertion.
AI providers (production, since 2026-10-17)
ENVIRONMENT=production; secrets ANTHROPIC_API_KEY (a key created inside a named Anthropic workspace — keys from
the Default workspace are rejected with "not scoped to a workspace") and OPENAI_API_KEY. Active chains: ASR
Workers AI whisper-large-v3-turbo → OpenAI whisper-1; LLM/content/QA Anthropic claude-sonnet-5-5 → OpenAI gpt-4.1;
TTS OpenAI gpt-4o-mini-tts → Workers AI MeloTTS; embeddings Workers AI bge-m3. The saved config lives in KV
ai:providers (Dashboard → Providers); the mock providers are refused under production.
Sign in with Apple (done 2026-10-17): secrets APPLE_TEAM_ID (Z9KD6Z92BF), APPLE_KEY_ID (XZ9YBHF9AH, key "Shuke
Sign in with Apple", primary App ID com.weizhi.shuke) and APPLE_PRIVATE_KEY (the one-time .p8; keep the backup
outside Downloads). Verified: a client secret signed with the key gets invalid_grant (not invalid_client) from
https://appleid.apple.com/auth/token, i.e. Apple accepts the key for this client id.
RevenueCat (purchases)
Set up 2026-10-17 in the RevenueCat dashboard: project Clarisay → app "Clarisay (App Store)" (com.weizhi.shuke,
In-App Purchase key T5AP5D989H, App Store Connect API key D9FG72BMZ7), products clarisay.pro.annual /
clarisay.pro.monthly, entitlement pro, offering default ($rc_annual, $rc_monthly). The app's public SDK key is in
ios/Config/Shared.xcconfig (REVENUECAT_API_KEY). The first product records (shuke.pro.*, created before the
Clarisay rename) are detached from pro and out of the offering; they never existed in App Store Connect and can be
deleted. Operator steps still open:
- App Store Connect: create the auto-renewable subscriptions
clarisay.pro.annual(7-day free trial intro offer) andclarisay.pro.monthlyin one subscription group. Prices (doubled from the first plan on 2026-10-17): annual $79.99 / ¥396, monthly $13.99 / ¥60 (US base price; use the nearest available price point where a storefront has no exact match). RevenueCat shows them as "Not found" until then. - RevenueCat → project Clarisay → API keys → New secret API key (v1), then
cd backend && pnpm wrangler secret put REVENUECAT_API_KEY. Secret keys are scoped to one RevenueCat project: a key from another project (JuiceUp, OS, …) cannot read Clarisay's customers. - Generate a webhook secret (
openssl rand -hex 32), store it withpnpm wrangler secret put REVENUECAT_WEBHOOK_AUTH(the exact header value, e.g.Bearer <hex>), and in RevenueCat → Integrations → Webhooks addhttps://api.clarisay.com/v1/webhooks/revenuecatwith that Authorization header value. Send the dashboard's test event; it should return 200. - Optional: RevenueCat app settings → Apple Server Notification URL → "Apply in App Store Connect", and set the
forwarding URL to
https://api.clarisay.com/v1/webhooks/appleso pre-RevenueCat builds stay current.
Without steps 2–3, /v1/subscriptions/revenuecat/sync answers 503 subscription_unavailable and the webhook 401.
Cloudflare Access for the dashboard (done 2026-10-17)
The dashboard calls the backend through a service binding (services: BACKEND → shuke-api in
dashboard/wrangler.jsonc): two Workers on one workers.dev subdomain cannot fetch each other (error 1042).
Zero Trust team dou-tech (dou-tech.cloudflareaccess.com), Access → Applications → shuke-dashboard
(self-hosted, destination = the shuke-dashboard Worker's production and preview URLs, application id
5e9e3eca-b81b-4c52-b406-07495cbb4f51), policy Allow owner (reusable, shared with the other dashboards),
session 24 h. ACCESS_TEAM_DOMAIN is set on both Workers and ACCESS_AUD on the backend (wrangler.jsonc vars).
- Unauthenticated
GET /on the dashboard → 302 to the Access login; the proxy forwardsCf-Access-Jwt-Assertionand never injects an admin key. - The backend verifies the assertion against the team's public keys and, under
ENVIRONMENT=production, refusesX-Admin-Key(401). Scripts that used the admin key against production (e.g.smoke.sh's admin section) no longer work there; run them against a localwrangler devor set up an Access service token and sendCF-Access-Client-Id/Secret. - To add an operator: Zero Trust → Access controls → Policies → Allow owner → add the email (or create a second policy and attach it to the application).
dash.clarisay.comis a public hostname destination of the same shuke-dashboard application (one application, so the AUD tagACCESS_AUDon the backend is unchanged). Unauthenticated requests get a 302 to the Access login.- Once operators use
dash.clarisay.com, set"workers_dev": falseindashboard/wrangler.jsonc, deploy, and remove the Worker URL destinations from the Access application.
Verification done on 2026-10-17
clarisay.com, after deploying c8dcb19 (backend 3353c750, landing 93476b3e, dashboard ab739534):
https://api.clarisay.com/healthand/v1/config200;/v1/paywallwithout a token 401; CORS preflight fromOrigin: https://clarisay.comreturnsAccess-Control-Allow-Origin: https://clarisay.com.https://clarisay.com/,/zh,/terms,/privacy,/sitemap.xml,/robots.txt,/api/health200; canonical and sitemap URLs usehttps://clarisay.com.https://www.clarisay.com/200.https://dash.clarisay.com/and/api/proxy/admin/overview→ 302 to thedou-techAccess login.- The
workers.devURLs still answer (backend/health200, dashboard 302 to Access).
First environment (workers.dev):
BASE=https://shuke-api.dou-tech.workers.dev SMOKE_ALLOW_NO_FETCH=1 pnpm smoke→smoke OK(anonymous auth, onboarding, upload, queue pipeline toassessed, settlement replay, paywall 402, brief → review → publish → public catalog). The source adaptation is skipped:CONTENT_FETCH_ENABLED=falseon the Worker (ADR-002) and the fixture server is local. The smoke article was unpublished and its sources archived afterwards.- Dashboard
/api/health200;/api/proxy/admin/overview|review|sources|quality200 with real data through the service binding (checked with a temporaryADMIN_KEY, since removed). - Landing
/,/zh,/privacy,/zh/support,/sitemap.xml,/api/health200;POST /api/waitlist201 (the test entry was deleted from KV).
Known gaps
CONTENT_FETCH_ENABLEDstaysfalseuntil the starter sources are reviewed (ADR-002).PUBLIC_BASE_URLis empty on purpose: media URLs use the request origin, so old builds onworkers.devand new builds onapi.clarisay.comeach get URLs on their own host.www.clarisay.comserves the same pages as the apex (canonical tags point at the apex); a Cloudflare Redirect Rule www → apex would make it a 301.- Worker, D1, R2, KV and queue names still say
shuke; renaming them is part of the brand rename, not the domain. - The backend's optional
WAITLISTKV binding (waitlist export) is not configured.