diff --git a/docs/BEST_PRACTICES/PLATFORM_PLAYBOOK.md b/docs/BEST_PRACTICES/PLATFORM_PLAYBOOK.md index 8fc2bde6..7a5fe31e 100644 --- a/docs/BEST_PRACTICES/PLATFORM_PLAYBOOK.md +++ b/docs/BEST_PRACTICES/PLATFORM_PLAYBOOK.md @@ -2,7 +2,7 @@ > **For:** Founders, engineers, and AI coding agents building products on the ByteLyst shared platform. > -> **Last updated:** 2026-02-28 · **Ecosystem:** 5 products, 16 shared packages, 43 platform modules, 1100+ service tests. +> **Last updated:** 2026-03-01 · **Ecosystem:** 6 products, 20 shared packages, 25 platform modules + 5 product backends, 1200+ service tests. --- @@ -57,47 +57,77 @@ The ByteLyst platform follows a **"build once, ship five"** model: ## 2. What You Get for Free -### 2.1 Shared Packages (16) +### 2.1 Shared Packages (20) Every package is `npm install`-able via `file:` refs to the sibling common-plat repo. -| Package | What It Does | You Write | -| ------------------------------ | ------------------------------------------------------ | ------------------------ | -| **@bytelyst/auth** | JWT sign/verify, password hash, auth middleware | Nothing — just call it | -| **@bytelyst/auth-client** | Client-side auth (login, register, token refresh, SSO) | Wire to your login UI | -| **@bytelyst/cosmos** | Cosmos DB client singleton, container registry | Register your containers | -| **@bytelyst/config** | Zod env loader, product identity, Key Vault resolver | Your product's `.env` | -| **@bytelyst/api-client** | Fetch wrapper with auth injection, timeout, retry | Your service URLs | -| **@bytelyst/errors** | Typed HTTP errors (400–429) | Throw them | -| **@bytelyst/fastify-core** | `createServiceApp()` + `startService()` factory | Your route plugins | -| **@bytelyst/react-auth** | `createAuthContext()` → typed Provider + useAuth hook | Wrap your app | -| **@bytelyst/logger** | Pino-based structured logging | `req.log.info(...)` | -| **@bytelyst/events** | Typed in-memory event bus with error isolation | Emit/subscribe | -| **@bytelyst/blob** | Azure Blob Storage client + SAS token helpers | Your container names | -| **@bytelyst/extraction** | AI text extraction client | Your extraction tasks | -| **@bytelyst/monitoring** | Health-check utilities, Loki/Grafana helpers | Your service name | -| **@bytelyst/telemetry-client** | Browser/RN telemetry SDK (queue, flush, beacon) | `trackEvent()` | -| **@bytelyst/design-tokens** | JSON → CSS/TS/Kotlin/Swift token generator | Your color overrides | -| **@bytelyst/testing** | Shared test mocks, Fastify inject helpers | Import in tests | +**Server-side packages (Node.js / Fastify):** -### 2.2 Platform Service Modules (43) +| Package | What It Does | You Write | +| -------------------------- | ---------------------------------------------------- | ------------------------ | +| **@bytelyst/auth** | JWT sign/verify, password hash, auth middleware | Nothing — just call it | +| **@bytelyst/cosmos** | Cosmos DB client singleton, container registry | Register your containers | +| **@bytelyst/config** | Zod env loader, product identity, Key Vault resolver | Your product's `.env` | +| **@bytelyst/api-client** | Fetch wrapper with auth injection, timeout, retry | Your service URLs | +| **@bytelyst/errors** | Typed HTTP errors (400–429) | Throw them | +| **@bytelyst/fastify-core** | `createServiceApp()` + `startService()` factory | Your route plugins | +| **@bytelyst/logger** | Pino-based structured logging | `req.log.info(...)` | +| **@bytelyst/events** | Typed in-memory event bus with error isolation | Emit/subscribe | +| **@bytelyst/blob** | Azure Blob Storage client + SAS token helpers | Your container names | +| **@bytelyst/extraction** | AI text extraction client | Your extraction tasks | +| **@bytelyst/monitoring** | Health-check utilities, Loki/Grafana helpers | Your service name | +| **@bytelyst/testing** | Shared test mocks, Fastify inject helpers | Import in tests | -The consolidated platform-service (port 4003) provides REST endpoints for: +**Client-side packages (Browser / React Native / Mobile):** -| Category | Modules | What You Get | -| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------- | -| **Identity** | auth, sessions | JWT auth, login/register, SSO, password reset, email verification, device sessions | -| **Billing** | subscriptions, plans, licenses, stripe, promos | Stripe integration, plan management, promo codes, license keys | -| **Growth** | invitations, referrals | Invite links, referral tracking, reward attribution | -| **Ops** | flags, maintenance, status, ip-rules, ratelimit, jobs | Feature flags with rollout %, maintenance mode, public status page, scheduled jobs | -| **Data** | audit, exports, settings, notifications, delivery, push-triggers | Audit log, GDPR export, user preferences, email/push delivery with templates | -| **Content** | items, comments, votes, public, changelog, feedback | Tracker/roadmap items, comments, voting, public roadmap, in-app feedback | -| **Telemetry** | telemetry, analytics, experiments | Event ingestion, error clustering, geo distribution, A/B experiments | -| **Storage** | blob, products | Blob SAS tokens, product registry/cache | -| **Impersonation** | impersonation | Admin user impersonation for debugging | -| **Product-specific** | timers, routines, shared-timers, households (CM), fasting-sessions, fasting-protocols, body-stages, social-fasting, meal-log (NG), brains, memory, reflections, daily-briefs, streaks (ML) | Domain CRUD — each scoped by `productId` | +| Package | What It Does | You Write | +| --------------------------------- | ------------------------------------------------------------ | --------------------- | +| **@bytelyst/auth-client** | Client-side auth (login, register, token refresh, SSO) | Wire to your login UI | +| **@bytelyst/platform-client** | Typed fetch wrapper with auth injection, x-request-id, retry | Your base URL | +| **@bytelyst/feature-flag-client** | Feature flag polling client with caching | Init + `isEnabled()` | +| **@bytelyst/kill-switch-client** | Kill switch check (force-update, maintenance mode) | Init at app launch | +| **@bytelyst/offline-queue** | Persistent retry queue with max retries and queue size | Storage adapter | +| **@bytelyst/react-auth** | `createAuthContext()` → typed Provider + useAuth hook | Wrap your app | +| **@bytelyst/telemetry-client** | Browser/RN telemetry SDK (queue, flush, beacon) | `trackEvent()` | +| **@bytelyst/design-tokens** | JSON → CSS/TS/Kotlin/Swift token generator | Your color overrides | -### 2.3 Dashboards (Product-Agnostic) +### 2.2 Platform Service Modules (25 — product-agnostic) + +The consolidated platform-service (port 4003) provides REST endpoints for shared infrastructure: + +| Category | Modules | What You Get | +| ----------------- | ----------------------------------------------------- | ---------------------------------------------------------------------------------- | +| **Identity** | auth, sessions | JWT auth, login/register, SSO, password reset, email verification, device sessions | +| **Billing** | subscriptions, plans, licenses, stripe, promos | Stripe integration, plan management, promo codes, license keys | +| **Growth** | invitations, referrals | Invite links, referral tracking, reward attribution | +| **Ops** | flags, maintenance, status, ip-rules, ratelimit, jobs | Feature flags with rollout %, maintenance mode, public status page, scheduled jobs | +| **Data** | audit, exports, settings, notifications, delivery | Audit log, GDPR export, user preferences, email/push delivery with templates | +| **Content** | items, comments, votes, public, changelog, feedback | Tracker/roadmap items, comments, voting, public roadmap, in-app feedback | +| **Telemetry** | telemetry, analytics, experiments | Event ingestion, error clustering, geo distribution, A/B experiments | +| **Storage** | blob, products | Blob SAS tokens, product registry/cache | +| **Impersonation** | impersonation | Admin user impersonation for debugging | + +### 2.3 Product-Specific Backends (NEW — migrated from platform-service) + +Product-specific modules now live in each product repo's `backend/` directory. This keeps domain logic close to the product code and platform-service product-agnostic. + +| Product | Port | Modules | Tests | +| -------------- | ---- | ----------------------------------------------------------------------------------------- | ----- | +| **PeakPulse** | 4010 | peak-sessions, peak-routes | 59 | +| **ChronoMind** | 4011 | timers, routines, shared-timers, households, webhooks | 198 | +| **JarvisJr** | 4012 | jarvis-agents, jarvis-sessions, jarvis-memory, jarvis-teams, marketplace | 198 | +| **NomGap** | 4013 | fasting-sessions, fasting-protocols, body-stages, social-fasting, meal-log, push-triggers | 152 | +| **MindLyst** | 4014 | brains, memory, reflections, daily-briefs, streaks | 59 | + +Each backend uses: + +- `@bytelyst/fastify-core` for app factory +- `@bytelyst/cosmos` for Cosmos DB +- `@bytelyst/auth` for JWT verification (same `JWT_SECRET` as platform-service) +- `@bytelyst/errors` for typed HTTP errors +- Validated env config via Zod (never read `process.env` directly for secrets) + +### 2.4 Dashboards (Product-Agnostic) | Dashboard | Port | What It Does | | --------------- | ---- | ---------------------------------------------------------------------------------------------- | @@ -106,7 +136,7 @@ The consolidated platform-service (port 4003) provides REST endpoints for: Both work for any product — they read `productId` from env and filter accordingly. -### 2.4 Infrastructure +### 2.5 Infrastructure - **Azure Cosmos DB** — multi-tenant via `productId` partition - **Azure Blob Storage** — 6 containers (audio, transcripts, attachments, avatars, releases, backups) @@ -216,95 +246,106 @@ Create the standard `src/lib/` wiring files (copy from any existing product): | **KMP + SwiftUI/Compose** | Maximum native performance | Shared Kotlin, native UI shells | | **Pure Native** | Single platform focus | SwiftUI (iOS) or Jetpack Compose (Android) | -For any native app: +For any native app, use the shared platform SDKs: -1. Copy `TelemetryService` from an existing product (Swift or Kotlin) -2. Copy `PlatformApiClient` / `PlatformSyncManager` for sync -3. Wire `@bytelyst/auth-client` (or native equivalent) for auth -4. Use design tokens from `@bytelyst/design-tokens` +**iOS / macOS / watchOS — ByteLystPlatformSDK (Swift Package):** -### Day 2: Product-Specific Backend Modules (2–4 hours per module) +1. Add `../learning_ai_common_plat/packages/swift-platform-sdk/` as a local SPM dependency +2. Create `Platform/` directory with thin wrappers (Config, KeychainHelper, TelemetryService, AuthService, etc.) +3. Wire `PlatformSyncManager` for cloud sync +4. Use design tokens from `@bytelyst/design-tokens` (auto-generated `*Theme.swift`) -Each domain module follows the same pattern: +**Android — kotlin-platform-sdk (Gradle module):** + +1. Add `../learning_ai_common_plat/packages/kotlin-platform-sdk` as a project dependency +2. Create a Hilt `PlatformModule` (or Koin `platformModule`) providing `BLPlatformConfig`, `BLAuthClient`, `BLTelemetryClient`, `BLFeatureFlagClient`, `BLKillSwitchClient` +3. Wire sync via `PlatformApiClient` + `SyncRepository` + +**React Native (Expo) — @bytelyst/\* TS packages:** + +1. Wire `@bytelyst/platform-client`, `@bytelyst/auth-client`, `@bytelyst/feature-flag-client`, `@bytelyst/kill-switch-client`, `@bytelyst/offline-queue` +2. Use MMKV as storage adapter for offline queue +3. Use design tokens from `@bytelyst/design-tokens` (auto-generated `tokens.ts`) + +### Day 2: Product-Specific Backend (2–4 hours per module) + +> **Key change (2026-03-01):** Product-specific modules now live in the product repo's `backend/` directory, NOT in platform-service. This keeps domain logic close to the product and platform-service product-agnostic. + +Scaffold the backend (copy from any existing product repo): ``` -services/platform-service/src/modules// -├── types.ts # Zod schemas + TypeScript interfaces -├── repository.ts # Cosmos DB CRUD (query, create, update, delete) -├── routes.ts # Fastify REST endpoints -└── .test.ts # Vitest tests +your-product/backend/ +├── src/ +│ ├── lib/ +│ │ ├── config.ts # Zod env schema (PORT, HOST, JWT_SECRET, COSMOS_*) +│ │ ├── cosmos.ts # Re-export from @bytelyst/cosmos +│ │ ├── cosmos-init.ts # Register + initialize Cosmos containers +│ │ ├── auth.ts # Re-export from @bytelyst/auth +│ │ ├── errors.ts # Re-export from @bytelyst/errors +│ │ └── request-context.ts # productId validation from JWT/headers +│ ├── modules/ +│ │ └── / +│ │ ├── types.ts # Zod schemas + TypeScript interfaces +│ │ ├── repository.ts # Cosmos DB CRUD +│ │ ├── routes.ts # Fastify REST endpoints +│ │ └── .test.ts # Vitest tests +│ └── server.ts # Entry point +├── package.json # file: refs to @bytelyst/* packages +├── tsconfig.json +├── vitest.config.ts +└── .env.example ``` -**Template (types.ts):** +**Template (server.ts):** ```typescript -import { z } from 'zod'; +import { createServiceApp, startService } from '@bytelyst/fastify-core'; +import { jwtVerify } from 'jose'; +import { yourRoutes } from './modules/your-module/routes.js'; +import { initCosmosIfNeeded } from './lib/cosmos-init.js'; +import { config } from './lib/config.js'; -export interface YourDoc { - id: string; - productId: string; // REQUIRED — always - userId: string; - // ... your fields - createdAt: string; - updatedAt: string; -} +// ⚠️ Always use validated config — never process.env directly for secrets +const jwtSecret = new TextEncoder().encode(config.JWT_SECRET); -export const CreateYourDocSchema = z.object({ - // ... your validation +await initCosmosIfNeeded(); +const app = await createServiceApp({ name: 'yourapp-backend', version: '0.1.0' }); + +app.addHook('onRequest', async req => { + const auth = req.headers.authorization; + if (!auth?.startsWith('Bearer ')) return; + try { + const { payload } = await jwtVerify(auth.slice(7), jwtSecret, { issuer: 'bytelyst-platform' }); + req.jwtPayload = payload; + } catch { + /* token invalid — leave undefined */ + } }); -``` -**Template (repository.ts):** - -```typescript -import { getContainer } from '../../lib/cosmos.js'; -import type { YourDoc } from './types.js'; - -function container() { - return getContainer('your_collection'); -} - -export async function list(productId: string, userId: string): Promise { - const { resources } = await container() - .items.query({ - query: 'SELECT * FROM c WHERE c.productId = @pid AND c.userId = @uid', - parameters: [ - { name: '@pid', value: productId }, - { name: '@uid', value: userId }, - ], - }) - .fetchAll(); - return resources; -} -// ... create, update, delete +await app.register(yourRoutes, { prefix: '/api' }); +await startService(app, { port: config.PORT, host: config.HOST }); ``` **Template (routes.ts):** ```typescript import type { FastifyInstance } from 'fastify'; -import { getRequestProductId } from '../../lib/request-context.js'; +import { extractAuth } from '../../lib/auth.js'; import { BadRequestError, NotFoundError } from '../../lib/errors.js'; import * as repo from './repository.js'; import { CreateYourDocSchema } from './types.js'; +const PRODUCT_ID = 'yourapp'; + export async function yourRoutes(app: FastifyInstance) { app.get('/your-resource', async req => { - const productId = getRequestProductId(req); - return { items: await repo.list(productId, req.user!.userId) }; + const auth = await extractAuth(req); + return { items: await repo.list(auth.sub, PRODUCT_ID) }; }); // ... POST, PUT, DELETE } ``` -Register in `server.ts`: - -```typescript -import { yourRoutes } from './modules/your-module/routes.js'; -// ... inside route registration block: -await app.register(yourRoutes); -``` - ### Day 3: User Dashboard (optional) — 4–6 hours If your product needs a user-facing web portal, create a Next.js app in your product repo: @@ -328,7 +369,7 @@ The admin dashboard and tracker dashboard are **already built and product-agnost ### What Took Months, Now Takes Days -Based on actual delivery across 5 products (LysnrAI, MindLyst, ChronoMind, NomGap, and the platform itself): +Based on actual delivery across 6 products (LysnrAI, MindLyst, ChronoMind, NomGap, JarvisJr, PeakPulse): | Capability | Without Platform | With Platform | Savings | | ------------------------------------------------ | ---------------- | --------------------------- | ------- | @@ -375,8 +416,16 @@ your-product/ │ │ ├── components/ # UI components │ │ └── lib/ # Engine logic (pure TS, no framework imports) │ └── package.json # file: refs to @bytelyst/* +├── backend/ # Product-specific Fastify backend (NEW) +│ ├── src/ +│ │ ├── lib/ # config, cosmos, auth, errors re-exports +│ │ ├── modules/ # Domain modules (types, repo, routes, tests) +│ │ └── server.ts # Entry point +│ └── package.json # file: refs to @bytelyst/* server packages ├── ios/ # SwiftUI (if native) +│ └── /Platform/ # Thin wrappers over ByteLystPlatformSDK ├── android/ # Jetpack Compose (if native) +│ └── app/.../platform/ # Hilt/Koin module for kotlin-platform-sdk ├── docs/ # PRD, roadmap, research ├── scripts/ # Build/deploy scripts ├── AGENTS.md # AI agent instructions @@ -415,11 +464,18 @@ Local Store (Zustand/MMKV/UserDefaults) └── On next online: flush queue → pull remote → merge ``` -**Key files to copy from existing products:** +**Shared packages for sync:** -- `offline-queue.ts` (NomGap) — MMKV-backed queue with retry + exponential backoff -- `platform-sync.ts` (ChronoMind) — full bidirectional sync with conflict detection -- `PlatformSyncManager.kt/swift` (MindLyst) — native sync managers +- `@bytelyst/offline-queue` — persistent retry queue with max retries and queue size (TS/RN) +- `@bytelyst/platform-client` — typed fetch wrapper with auth injection, x-request-id, auto-retry on 401 (TS/RN) +- `ByteLystPlatformSDK.BLSyncEngine` — offline-first sync engine (Swift/iOS) +- `kotlin-platform-sdk` — platform API client + secure store (Kotlin/Android) + +**Reference implementations:** + +- `PlatformSyncManager.swift` (ChronoMind) — bidirectional sync with conflict detection, offline queue +- `PlatformSyncManager.swift` (PeakPulse) — BLSyncEngine-based upload/download +- `src/api/client.ts` (NomGap) — `@bytelyst/platform-client` wrapper with auth token injection --- @@ -510,6 +566,9 @@ Client → 401 → silent refresh via refresh token → retry ```typescript // lib/auth-server.ts — copy from existing product import { createJwtUtils } from '@bytelyst/auth'; + +// ⚠️ For product backends (Fastify): use validated Zod config, never process.env directly +// For Next.js API routes: process.env is acceptable since Next.js validates at build time export const { verifyToken, signToken, hashPassword, verifyPassword } = createJwtUtils({ secret: process.env.JWT_SECRET!, }); @@ -740,12 +799,12 @@ NomGap (fasting visualization app) was built on this platform in ~1 week: ChronoMind (AI clock/timer) spans web (Next.js PWA), iOS (SwiftUI), Android (Compose), watchOS, macOS: 1. **Web:** Next.js 16 + Zustand + Serwist (PWA). 16 pure engine modules in `web/src/lib/`, 12 React components, 394 tests -2. **iOS:** SwiftUI with 20+ shared modules in `ios/ChronoMind/Shared/`, WidgetKit, Live Activities, Siri Shortcuts -3. **Android:** Jetpack Compose + Room + Hilt, Glance widgets, foreground service -4. **Backend:** 4 modules in platform-service (timers, routines, shared-timers, households) -5. **Sync:** Bidirectional sync with offline queue, conflict detection +2. **iOS:** SwiftUI with 20+ shared modules in `ios/ChronoMind/Shared/`, `Platform/` wrappers over ByteLystPlatformSDK, WidgetKit, Live Activities, Siri Shortcuts +3. **Android:** Jetpack Compose + Room + Hilt + kotlin-platform-sdk, Glance widgets, foreground service +4. **Backend:** Product-specific Fastify backend (port 4011) with 5 modules (timers, routines, shared-timers, households, webhooks) — 198 tests +5. **Sync:** Bidirectional sync with offline queue, conflict detection, Keychain-based auth token storage -**Total platform-service modules added:** 4. Everything else (auth, billing, flags, telemetry, admin, tracker) was inherited. +**Total product backend modules:** 5. Everything else (auth, billing, flags, telemetry, admin, tracker) was inherited from platform-service. --- @@ -760,8 +819,14 @@ ChronoMind (AI clock/timer) spans web (Next.js PWA), iOS (SwiftUI), Android (Com □ Add design tokens to packages/design-tokens/ (optional) □ Scaffold web app with file: refs to @bytelyst/* □ Create lib/ wiring: cosmos.ts, auth-server.ts, product-config.ts, platform-client.ts -□ Create 1–3 backend modules in platform-service -□ Register Cosmos containers in cosmos-init.ts +□ Scaffold product backend in backend/ (copy from existing product repo) +□ — lib/config.ts with Zod env validation (JWT_SECRET from config, NOT process.env) +□ — lib/cosmos-init.ts to register + initialize Cosmos containers +□ — 1–3 domain modules (types.ts, repository.ts, routes.ts, *.test.ts) +□ Wire native SDKs: +□ — iOS: ByteLystPlatformSDK → Platform/ wrappers +□ — Android: kotlin-platform-sdk → Hilt/Koin module +□ — RN: @bytelyst/platform-client, auth-client, feature-flag-client, kill-switch-client, offline-queue □ Wire telemetry client □ Wire feature flag polling □ Add offline queue for sync