learning_ai_common_plat/docs/ECOSYSTEM_ARCHITECTURE.md
saravanakumardb1 90b9cf93d8 fix(common): configure ESLint 9 and fix lint issues
- Added @eslint/js dependency
- Updated eslint.config.js for ESLint 9 compatibility
- Added required globals (crypto, localStorage, React, etc.)
- Fixed unused imports and variables
- Disabled sort-imports temporarily
- Formatted all files with Prettier
2026-02-12 16:37:30 -08:00

39 KiB

ByteLyst Ecosystem — Post-Refactor Architecture

Companion diagram: ecosystem-after-refactor.drawio (open in draw.io or VS Code draw.io extension)

Date: 2026-02-12 · Author: Cascade


Table of Contents

  1. Ecosystem Overview
  2. Three-Repo Structure
  3. Common Platform Packages — Detailed
  4. Component & Service Inventory
  5. Dependency Graph
  6. Migration Plan
  7. Advantages
  8. Cautions & Risks
  9. Versioning Strategy
  10. Testing Strategy
  11. CI/CD Impact
  12. Decision Log

1. Ecosystem Overview

The ByteLyst ecosystem consists of two product repos and one shared infrastructure repo:

┌─────────────────────────────────────────────────────────────────────────┐
│                        CLIENT APPLICATIONS                              │
│  LysnrAI Desktop · iOS · Android · Admin · User Portal · Tracker       │
│  MindLyst iOS · Android · Web                                           │
└───────────────────────────────┬──────────────────────────────────────────┘
                                │
┌──────────────────┐  ┌────────┴────────┐  ┌──────────────────────────────┐
│  LysnrAI Repo    │  │ Common Platform │  │  MindLyst Repo               │
│  (voice_ai_agent)│←─│ (common_plat)   │─→│  (multimodal_memory_agents)  │
│                  │  │ @bytelyst/*     │  │                              │
│  4 Fastify svcs  │  │ 8 npm packages  │  │  KMP shared module           │
│  3 Next.js apps  │  │                 │  │  3 native apps               │
│  1 FastAPI       │  │ Design tokens   │  │  1 Next.js web               │
│  1 Desktop app   │  │ (JSON → all)    │  │  design-system/              │
│  2 Mobile apps   │  │                 │  │                              │
└────────┬─────────┘  └─────────────────┘  └──────────────┬───────────────┘
         │                                                 │
┌────────┴─────────────────────────────────────────────────┴───────────────┐
│                     AZURE CLOUD INFRASTRUCTURE                           │
│  Cosmos DB · Blob Storage · Key Vault · Speech · OpenAI · Stripe        │
│  Docker Compose (Traefik · Loki · Grafana) · GitHub Actions              │
└──────────────────────────────────────────────────────────────────────────┘

2. Three-Repo Structure

2.1 learning_voice_ai_agent (LysnrAI)

Purpose: Cross-platform voice-to-text dictation platform.

Component Tech Port Description
Desktop App Python 3.12, tkinter macOS/Windows dictation app
FastAPI Backend Python 3.12 8000 REST API, cloud sync
platform-service Fastify + TS 4003 Auth, audit, flags, notifications, blob, rate limiting
billing-service Fastify + TS 4002 Subscriptions, plans, usage, licenses, Stripe
growth-service Fastify + TS 4001 Invitations, referrals, promo codes
tracker-service Fastify + TS 4004 Feature requests, bugs, votes, public roadmap
admin-dashboard-web Next.js 16 3001 Admin panel (users, tokens, audit, themes)
user-dashboard-web Next.js 16 3002 User portal (profile, billing, settings, SSO)
tracker-dashboard-web Next.js 3003 Tracker board, kanban, public roadmap
iOS App Swift + SwiftUI Native (no KMP)
Android App Kotlin + Compose Native (no KMP)
Monitoring Loki + Grafana Health checks, logs, metrics

2.2 learning_multimodal_memory_agents (MindLyst)

Purpose: Role-based Life OS — multimodal second-brain with AI triage.

Component Tech Description
KMP Shared Module Kotlin Multiplatform Models, repositories, DI, triage pipeline, API clients
iOS App SwiftUI + KMP Native UI consuming shared Kotlin module
Android App Jetpack Compose + KMP Native UI consuming shared Kotlin module
Web Dashboard Next.js 14 Pages Router, CSS custom properties, landing + dashboard
design-system/ JSON + CSS Canonical tokens, component specs

2.3 learning_ai_common_plat (Common Platform)

Purpose: Shared npm packages consumed by both product repos.

Package Type Primary Consumers
@bytelyst/errors TypeScript All 4 Fastify services, dashboards
@bytelyst/cosmos TypeScript All 4 Fastify services, 2 Next.js dashboards
@bytelyst/config TypeScript All 4 Fastify services
@bytelyst/auth TypeScript All services + dashboards with JWT
@bytelyst/fastify-core TypeScript All 4 Fastify services
@bytelyst/api-client TypeScript All 3 Next.js dashboards
@bytelyst/react-auth TypeScript/React All 3 Next.js dashboards
@bytelyst/design-tokens JSON → multi-format MindLyst (all platforms), potentially LysnrAI dashboards

3. Common Platform Packages — Detailed

3.1 @bytelyst/errors (P0)

What it replaces: 4 separate errors.ts files across services (157 LOC total).

// Unified error hierarchy
export class ServiceError extends Error {
  constructor(public statusCode: number, message: string) { ... }
}
export class NotFoundError extends ServiceError { /* 404 */ }
export class BadRequestError extends ServiceError { /* 400 */ }
export class UnauthorizedError extends ServiceError { /* 401 */ }
export class ForbiddenError extends ServiceError { /* 403 */ }
export class ConflictError extends ServiceError { /* 409 */ }
export class TooManyRequestsError extends ServiceError { /* 429 */ }

Design decision: Superset of all error types currently used. Services import only what they need. The details property (used by billing-service's TooManyRequests) is added to the base class as optional.

Dependencies: None (leaf package).


3.2 @bytelyst/cosmos (P0)

What it replaces: 6 separate cosmos.ts files (306 LOC total).

// Core exports
export function getCosmosClient(): CosmosClient;
export function getDatabase(dbName?: string): Database;
export function getContainer(name: string): Container;

// Container registry (dashboards use this)
export function registerContainers(config: Record<string, ContainerConfig>): void;
export function initializeAllContainers(): Promise<void>;

// Types
export interface ContainerConfig {
  partitionKeyPath: string;
  defaultTtl?: number | null;
}

Design decision: The simple getContainer(name) used by microservices works alongside the full registry used by dashboards. Services call getContainer() directly; dashboards call registerContainers() first to set up the partition key map.

Dependencies: @azure/cosmos (peer dependency).


3.3 @bytelyst/config (P1)

What it replaces: 4 separate config.ts + 4 separate product-config.ts (142 LOC total).

// Base env schema — every Fastify service needs these
export const baseEnvSchema = z.object({
  PORT: z.coerce.number().default(3000),
  HOST: z.string().default('0.0.0.0'),
  NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
  CORS_ORIGIN: z.string().optional(),
  SERVICE_NAME: z.string(),
  COSMOS_ENDPOINT: z.string().min(1),
  COSMOS_KEY: z.string().min(1),
  COSMOS_DATABASE: z.string().default('lysnrai'),
});

// Extend per-service
export function loadConfig<T extends z.ZodRawShape>(extension: T) {
  return baseEnvSchema.extend(extension).parse(process.env);
}

// Product identity
export function loadProductIdentity(path?: string): ProductIdentity;
export interface ProductIdentity {
  productId: string;
  displayName: string;
  licensePrefix: string;
  configDirName: string;
  envVarPrefix: string;
}

Design decision: Each service calls loadConfig({ STRIPE_SECRET_KEY: z.string(), ... }) to extend the base. The product identity reads from shared/product.json or falls back to env vars, eliminating the hardcoded product-config.ts copies.

Dependencies: zod (peer dependency).


3.4 @bytelyst/auth (P1)

What it replaces: 5 separate JWT/auth files (250 LOC total).

// JWT operations (configurable issuer + expiry)
export function createJwtUtils(options: {
  issuer: string;
  accessTokenExpiry?: string; // default "1h"
  refreshTokenExpiry?: string; // default "30d"
}): {
  createAccessToken(payload: TokenPayload): Promise<string>;
  createRefreshToken(payload: { sub: string }): Promise<string>;
  verifyToken(token: string): Promise<TokenPayload | null>;
};

// Fastify middleware
export function extractAuth(req: FastifyRequest): Promise<AuthPayload>;
export function requireRole(...roles: string[]): FastifyHook;

// Next.js server-side
export function getCurrentUser(
  authHeader: string | null,
  getUserById: Function
): Promise<UserDoc | null>;

// Password hashing
export function hashPassword(plain: string): Promise<string>;
export function verifyPassword(plain: string, hash: string): Promise<boolean>;

// Types
export interface AuthPayload {
  sub: string;
  email?: string;
  role?: string;
  productId?: string;
  type?: string;
}
export interface TokenPayload {
  sub: string;
  email: string;
  role: string;
  type: 'access' | 'refresh';
}

Design decision: The createJwtUtils() factory pattern allows each service to configure its own issuer (lysnrai-admin, lysnrai-user, lysnrai) while sharing all the logic. Platform-service creates tokens; other services only verify. The requireRole() hook is a new addition that multiple services currently implement inline.

Dependencies: jose, bcryptjs (peer dependencies).


3.5 @bytelyst/fastify-core (P1)

What it replaces: 4 separate server.ts files (351 LOC total).

export async function createServiceApp(options: {
  name: string;
  version: string;
  description: string;
  port?: number;
  corsOrigin?: string;
}): Promise<FastifyInstance> {
  const app = Fastify({ logger: true });

  // CORS
  await app.register(cors, { origin: ... });

  // OpenAPI / Swagger
  await app.register(swagger, { openapi: { info: { title, version, description } } });

  // Prometheus metrics
  await app.register(metricsPlugin, { endpoint: "/metrics" });

  // x-request-id propagation
  app.addHook("onRequest", requestIdHook);

  // Health endpoint
  app.get("/health", healthHandler(options.name, options.version));

  // ServiceError-aware error handler
  app.setErrorHandler(serviceErrorHandler);

  return app;
}

export async function startService(app: FastifyInstance, port: number, host?: string): Promise<void>;

Design decision: After calling createServiceApp(), each service just registers its route modules and calls startService(). The server.ts in each service shrinks from ~85 lines to ~15 lines. The healthHandler returns the standard { status, service, version, timestamp, requestId } shape that the monitoring health-check script expects.

Dependencies: fastify, @fastify/cors, @fastify/swagger, fastify-metrics, @bytelyst/errors, @bytelyst/config (peer dependencies).


3.6 @bytelyst/api-client (P2)

What it replaces: 5+ separate fetch wrapper implementations (200 LOC total).

export function createApiClient(options: {
  baseUrl: string;
  getToken?: () => string | null;
  defaultHeaders?: Record<string, string>;
}): {
  // Throws on error (for service-to-service)
  fetch<T>(path: string, options?: RequestInit): Promise<T>;
  // Returns { data, error } tuple (for UI components)
  safeFetch<T>(
    path: string,
    options?: RequestInit
  ): Promise<{ data: T | null; error: string | null }>;
};

Design decision: Two fetch modes: throwing (used by server-side service clients like billing-client.ts, growth-client.ts) and safe (used by React components via api.ts). The getToken() callback handles auth header injection without coupling to any specific storage mechanism.

Dependencies: None (uses native fetch).


3.7 @bytelyst/react-auth (P2)

What it replaces: 3 separate auth-context.tsx files (450 LOC total).

export function createAuthProvider<TUser extends BaseUser>(config: {
  storagePrefix: string; // "admin" | "portal" | "tracker"
  loginEndpoint: string; // "/api/auth/login"
  mapResponseToUser: (data: any) => TUser;
  enableSSO?: boolean;
}): {
  AuthProvider: React.FC<{ children: ReactNode }>;
  useAuth: () => AuthContextValue<TUser>;
};

export interface BaseUser {
  id?: string;
  email: string;
  name: string;
  role: string;
}

export interface AuthContextValue<TUser> {
  user: TUser | null;
  isAuthenticated: boolean;
  isLoading: boolean;
  login: (email: string, password: string) => Promise<boolean>;
  logout: () => void;
  getAccessToken: () => string | null;
}

Design decision: Factory pattern produces a fully-typed AuthProvider + useAuth() pair. Each dashboard provides its own TUser type and storage prefix. SSO cookie support (used by user-dashboard) is opt-in via enableSSO. Registration support (also user-dashboard only) can be added as an optional config callback.

Dependencies: react (peer dependency), @bytelyst/api-client.


3.8 @bytelyst/design-tokens (P3)

What it replaces: 5 manually-synced token files across MindLyst.

packages/design-tokens/
├── tokens/
│   └── bytelyst.tokens.json    ← SINGLE SOURCE OF TRUTH
├── generated/                   ← Auto-generated (committed or gitignored)
│   ├── tokens.css               ← CSS custom properties (--ml-*)
│   ├── tokens.ts                ← TypeScript constants
│   ├── tokens.kt                ← Kotlin object (MindLystTokens)
│   └── tokens.swift             ← Swift structs (MindLystColors/Spacing/Radius)
├── scripts/
│   └── generate.ts              ← Reads JSON, emits all formats
└── package.json

Design decision: The canonical bytelyst.tokens.json (migrated from MindLyst's existing mindlyst.tokens.json) is the single source. A generation script produces platform-specific files. Consumers either:

  • Import the generated file directly (CSS, TS)
  • Copy the generated file into their project as a build step (Kotlin, Swift)

This eliminates the manual sync problem where token changes require updating 5 files.

Dependencies: None at runtime; typescript for the generation script.


4. Component & Service Inventory

4.1 All Services (Post-Refactor)

Service Repo Port Shared Packages Used
platform-service LysnrAI 4003 errors, cosmos, config, auth, fastify-core
billing-service LysnrAI 4002 errors, cosmos, config, fastify-core
growth-service LysnrAI 4001 errors, cosmos, config, fastify-core
tracker-service LysnrAI 4004 errors, cosmos, config, auth, fastify-core
FastAPI Backend LysnrAI 8000 (Python — separate concern)
admin-dashboard LysnrAI 3001 cosmos, auth, api-client, react-auth
user-dashboard LysnrAI 3002 cosmos, auth, api-client, react-auth
tracker-dashboard LysnrAI 3003 api-client, react-auth
MindLyst Web MindLyst 3050 design-tokens
MindLyst iOS MindLyst design-tokens (generated Swift)
MindLyst Android MindLyst design-tokens (generated Kotlin)

4.2 Azure Infrastructure

Service Resource Used By
Cosmos DB cosmos-mywisprai (Serverless) All TS services + dashboards
Blob Storage bytelystblobs (6 containers) platform-service, desktop, backend
Key Vault kv-mywisprai Desktop, mobile, backend
Speech mywisprai-speech (F0) Desktop, LysnrAI mobile
Azure OpenAI mywisprai-openai-sweden (S0) Desktop, backend
OpenAI API api.openai.com MindLyst KMP (triage, Whisper)
Stripe Payments API billing-service, growth-service

4.3 DevOps Components

Component Location Scope
Docker Compose LysnrAI root All services + Loki + Grafana + Traefik
Health Check services/monitoring/ Aggregates all /health endpoints
GitHub Actions .github/workflows/ (9 files) Per-service CI
run-local-all-services.sh LysnrAI root Dev startup script

5. Dependency Graph

@bytelyst/design-tokens  (standalone — no deps)
        │
        └─→ MindLyst iOS/Android/Web

@bytelyst/errors  (standalone — no deps)
        │
        ├─→ @bytelyst/fastify-core
        ├─→ All 4 Fastify services
        └─→ All 3 Next.js dashboards (via API routes)

@bytelyst/cosmos  (depends on: @azure/cosmos)
        │
        ├─→ All 4 Fastify services
        └─→ admin-dashboard, user-dashboard

@bytelyst/config  (depends on: zod)
        │
        ├─→ @bytelyst/fastify-core
        ├─→ @bytelyst/auth
        └─→ All 4 Fastify services

@bytelyst/auth  (depends on: jose, bcryptjs, @bytelyst/config)
        │
        ├─→ @bytelyst/api-client
        ├─→ platform-service (creates tokens)
        ├─→ tracker-service (verifies tokens)
        └─→ admin/user dashboards (server-side auth)

@bytelyst/fastify-core  (depends on: fastify, @bytelyst/errors, @bytelyst/config)
        │
        └─→ All 4 Fastify services

@bytelyst/api-client  (no runtime deps)
        │
        ├─→ @bytelyst/react-auth
        └─→ All 3 dashboards

@bytelyst/react-auth  (depends on: react, @bytelyst/api-client)
        │
        └─→ All 3 Next.js dashboards

Key insight: The graph is a clean DAG (directed acyclic graph) with no circular dependencies. Leaf packages (errors, cosmos, design-tokens) can be extracted first with zero risk.


6. Migration Plan

Phase 1: Foundation (Week 1)

  1. Initialize learning_ai_common_plat as pnpm workspace monorepo

    • pnpm init, pnpm-workspace.yaml, tsconfig.base.json
    • Shared vitest config, ruff.toml (for future Python packages)
  2. Extract @bytelyst/errors (P0, 1 hour)

    • Copy platform-service/src/lib/errors.ts as starting point
    • Add ConflictError (from tracker) and TooManyRequestsError (from billing)
    • Add optional details field to base ServiceError
    • Write tests (port from service tests)
    • Update all 4 services: import { ServiceError, ... } from "@bytelyst/errors"
  3. Extract @bytelyst/cosmos (P0, 2 hours)

    • Merge service version (simple) + dashboard version (registry + TTL)
    • Parameterize default database name
    • Write tests (mock Cosmos client)
    • Update all 6 consumers

Phase 2: Service Infrastructure (Week 2)

  1. Extract @bytelyst/config (P1, 2 hours)

    • Create baseEnvSchema from common fields
    • Add loadConfig() extension function
    • Add loadProductIdentity() from shared/product.json
    • Update all 4 services + remove 4 product-config.ts files
  2. Extract @bytelyst/auth (P1, 3 hours)

    • Create createJwtUtils() factory
    • Port extractAuth() Fastify hook
    • Port getCurrentUser() for Next.js
    • Port hashPassword() / verifyPassword()
    • Critical: test all auth flows end-to-end after migration
  3. Extract @bytelyst/fastify-core (P1, 3 hours)

    • Create createServiceApp() factory
    • Port request-id hook, health handler, error handler
    • Refactor all 4 server.ts files to use factory
    • Verify Docker builds still pass

Phase 3: Dashboard Libraries (Week 3)

  1. Extract @bytelyst/api-client (P2, 2 hours)

    • Create createApiClient() with both fetch modes
    • Update dashboard client files to use shared client
    • Service-specific methods stay in their respective client files
  2. Extract @bytelyst/react-auth (P2, 3 hours)

    • Create createAuthProvider() factory
    • Port SSO cookie logic as opt-in
    • Update all 3 dashboard auth contexts
    • Test login/logout flows in each dashboard

Phase 4: Design System (Week 4+)

  1. Extract @bytelyst/design-tokens (P3, 4 hours)
    • Migrate mindlyst.tokens.json as canonical source
    • Write generation script for CSS, TS, Kotlin, Swift
    • Add to MindLyst build pipeline
    • Consider adopting for LysnrAI dashboards (future)

7. Advantages

7.1 Immediate Benefits

Benefit Impact Measurement
~1,580 LOC eliminated Less code to maintain 8 fewer files per service feature
Single-point security fixes JWT/auth vulnerability fixed once, applied everywhere 1 PR instead of 5
Consistent error handling All services return identical error shapes 0 divergence
Faster new service creation createServiceApp() → 15 lines to launch Minutes vs hours
Cosmos DB config in one place Container registry + TTL managed centrally 1 file vs 6

7.2 Medium-Term Benefits

Benefit Impact
MindLyst backend bootstrap When MindLyst adds backend services, it gets Fastify infra for free
Consistent design language Token generation ensures iOS/Android/Web never drift
Cross-product features Tracker service already product-agnostic; shared auth enables MindLyst to use it
Onboarding speed New developer learns one pattern, applies to all services
Testability Shared packages have isolated tests; consumer tests focus on business logic

7.3 Architectural Benefits

Benefit Description
Separation of concerns Infrastructure (how) separated from business logic (what)
Dependency inversion Services depend on abstractions (@bytelyst/auth) not implementations
DRY principle No more copy-paste-modify cycle for new services
Contract stability Shared types ensure API contracts don't drift between services
Upgrade path Update jose or @azure/cosmos in one place

8. Cautions & Risks

8.1 HIGH RISK — Tightly Coupled Releases

Risk Description Mitigation
Breaking change cascade A breaking change in @bytelyst/auth breaks all services simultaneously Semantic versioning + file: references pin to exact commits. Run all consumer tests before publishing.
Diamond dependency Two packages depend on different versions of a shared dep Use peer dependencies + strict version ranges. pnpm handles this well.
Deployment ordering Must deploy common-plat before consuming repos can use new features Use git tags/releases. Consumers reference a specific version or commit.

8.2 MEDIUM RISK — Development Friction

Risk Description Mitigation
Cross-repo PRs A feature spanning common-plat + LysnrAI requires 2 PRs Use file:../learning_ai_common_plat/packages/X in dev, pin to version in CI.
Local dev setup Developers need all 3 repos cloned side-by-side Document in README. Add a setup script. Consider git submodules as alternative.
IDE navigation "Go to definition" may not work across repos TypeScript project references or workspace-level tsconfig.json can help.
Over-abstraction Temptation to extract things that aren't truly shared Rule: only extract when ≥2 consumers exist AND the code is >90% identical.

8.3 LOW RISK — Operational

Risk Description Mitigation
npm registry dependency Publishing to GitHub Packages adds infra complexity Start with file: references (zero infra). Upgrade to registry only if team grows.
Test isolation Shared package tests may not catch consumer-specific issues Each consumer repo keeps integration tests. Shared packages only unit-test their own API.
Python exclusion The desktop app and FastAPI backend can't use TypeScript packages Python remains independent. If patterns emerge (e.g., Cosmos client), create a Python equivalent later.
KMP exclusion MindLyst's Kotlin code can't use npm packages directly Only design-tokens applies to KMP (via generated Kotlin). All other packages are TS-only.

8.4 Things to AVOID

  1. Don't extract too early — Wait until code is stable and duplicated in ≥2 places
  2. Don't create a "utils" dumping ground — Every package must have a clear, single responsibility
  3. Don't abstract configuration — Each service's config.ts should remain in the service; only the base schema is shared
  4. Don't share React components — UI components are product-specific; only infrastructure (auth, fetch) is shared
  5. Don't break existing imports in one big PR — Migrate one package at a time, keep old imports working temporarily

9. Versioning Strategy

// In LysnrAI's services/platform-service/package.json
{
  "dependencies": {
    "@bytelyst/errors": "file:../../../learning_ai_common_plat/packages/errors",
    "@bytelyst/cosmos": "file:../../../learning_ai_common_plat/packages/cosmos",
  },
}

Pros: Zero infrastructure, works locally, pnpm install resolves it. Cons: All repos must be cloned side-by-side. Docker builds need a multi-stage approach or volume mount.

Future: GitHub Packages (Phase 2+)

{
  "dependencies": {
    "@bytelyst/errors": "^1.0.0",
    "@bytelyst/cosmos": "^1.0.0",
  },
}

Pros: True version pinning, CI-friendly, no local path dependency. Cons: Requires GitHub Packages setup, publish workflow, access tokens.

Versioning Rules

  • MAJOR (1.0 → 2.0): Breaking change to public API (renamed export, removed function, changed return type)
  • MINOR (1.0 → 1.1): New export, new optional parameter, new error subclass
  • PATCH (1.0.0 → 1.0.1): Bug fix, internal refactor, dependency update

10. Testing Strategy

Package-Level Tests

Each @bytelyst/* package has its own vitest test suite:

Package Test Focus
errors Error class hierarchy, statusCode correctness, instanceof checks
cosmos Client singleton behavior, container registry, env var reading (mocked)
config Zod schema validation, defaults, extension merging
auth JWT sign/verify round-trip, expiry, issuer validation, bcrypt hashing
fastify-core App factory produces correct hooks/routes, health endpoint shape
api-client Fetch wrapper behavior with mock server, error handling, auth injection
react-auth Provider renders, login/logout flows, localStorage persistence
design-tokens Generated output matches expected format for each platform

Consumer-Level Tests

Each service/dashboard keeps its existing test suite. After migration:

  • Service tests verify that imported shared functions work correctly in context
  • No need to re-test JWT internals — that's the package's job
  • Focus on integration: "Does my route handler correctly call extractAuth() and get the right payload?"

CI Matrix

common-plat CI:
  → Run all 8 package test suites
  → Run type-check (tsc --noEmit)
  → Run lint (ruff for future Python packages)

LysnrAI CI (per-service):
  → Install common-plat packages (file: or registry)
  → Run service tests
  → Run Next.js build

MindLyst CI:
  → Build KMP shared module
  → Build Next.js web
  → Verify generated tokens match source JSON

11. CI/CD Impact

Docker Build Changes

Services currently build independently. After extraction:

Option A: Multi-stage with copy (recommended for file: references)

# Copy common-plat packages into build context
COPY ../learning_ai_common_plat/packages/errors /common/errors
COPY ../learning_ai_common_plat/packages/cosmos /common/cosmos
# Adjust package.json to point to /common/*

Option B: Pre-publish (recommended for registry)

# Common-plat packages are already on npm registry
RUN npm install  # resolves @bytelyst/* from GitHub Packages

Option C: Monorepo build context (if repos are merged or submoduled)

# Build context includes all repos
COPY . /workspace
WORKDIR /workspace/learning_voice_ai_agent/services/platform-service
RUN npm install && npm run build

docker-compose.yml Changes

The existing docker-compose.yml may need an additional volume mount or build context adjustment:

services:
  platform-service:
    build:
      context: . # May need to widen to parent directory
      dockerfile: services/platform-service/Dockerfile

GitHub Actions Changes

  • New workflow: ci-common-plat.yml — tests all shared packages on push to learning_ai_common_plat
  • Modified workflows: Each service CI installs common-plat packages before running tests
  • Potential: A "consumer test" job in ci-common-plat.yml that runs key consumer tests after package changes

12. Decision Log

Decision Rationale Alternatives Considered
pnpm workspace monorepo for common-plat Simplest multi-package management, strict dep resolution npm workspaces (less strict), Turborepo (overkill for 8 packages), Nx (too heavy)
file: references initially Zero infrastructure, works immediately git submodules (complex merge), npm registry (needs setup), copy-paste (defeats purpose)
Factory pattern for fastify-core, auth, react-auth Services need slightly different config (port, issuer, user type) but identical boilerplate Inheritance (fragile), config objects (less type-safe), template codegen (complex)
Peer dependencies for heavy libs Prevents version conflicts (e.g., two versions of @azure/cosmos in one service) Direct deps (risk duplicate bundles), optional deps (too loose)
TypeScript-only packages (no Python) Python desktop/backend have less duplication and different patterns Shared Python package (only 2 consumers, not worth the packaging complexity yet)
Design tokens as JSON → generated code JSON is the universal format; generation ensures consistency Manual sync (error-prone), Figma API (adds dependency), CSS-only (excludes native)
NOT extracting React UI components UI is product-specific; only infrastructure is truly shared Shared component library (premature — products have different UX)
NOT merging repos Three distinct products with different release cadences and teams Monorepo (coupling), git submodules (merge conflicts)

Appendix A: File Counts Per Service (Before vs After)

Before Refactor

services/platform-service/src/lib/
  ├── config.ts           (32 lines)
  ├── cosmos.ts           (25 lines)
  ├── errors.ts           (38 lines)
  ├── product-config.ts   (9 lines)
  └── blob.ts             (service-specific, stays)

services/billing-service/src/lib/
  ├── config.ts           (34 lines)  ← DUPLICATE
  ├── cosmos.ts           (25 lines)  ← DUPLICATE
  ├── errors.ts           (41 lines)  ← DUPLICATE
  └── product-config.ts   (9 lines)   ← DUPLICATE

services/growth-service/src/lib/
  ├── config.ts           (25 lines)  ← DUPLICATE
  ├── cosmos.ts           (25 lines)  ← DUPLICATE
  ├── errors.ts           (35 lines)  ← DUPLICATE
  └── product-config.ts   (11 lines)  ← DUPLICATE

services/tracker-service/src/lib/
  ├── auth.ts             (52 lines)  ← DUPLICATE
  ├── config.ts           (24 lines)  ← DUPLICATE
  ├── cosmos.ts           (25 lines)  ← DUPLICATE
  ├── errors.ts           (44 lines)  ← DUPLICATE
  └── product-config.ts   (7 lines)   ← DUPLICATE

After Refactor

services/platform-service/src/lib/
  ├── blob.ts             (service-specific, stays)
  └── (all others → @bytelyst/*)

services/billing-service/src/lib/
  └── (empty — all moved to @bytelyst/*)

services/growth-service/src/lib/
  └── (empty — all moved to @bytelyst/*)

services/tracker-service/src/lib/
  └── (empty — all moved to @bytelyst/*)

Each server.ts: ~85 lines → ~15 lines
Each config.ts: ~30 lines → ~5 lines (just the extension schema)

Appendix B: Ecosystem Metrics

Metric Before After Delta
Duplicated infrastructure LOC ~1,856 ~275 -85%
Number of errors.ts files 4 1 -75%
Number of cosmos.ts files 6 1 -83%
Number of auth/jwt files 5 1 -80%
Number of server.ts boilerplate lines 351 ~60 -83%
Time to create new Fastify service ~2 hours ~20 min -83%
Time to fix a JWT vulnerability 5 PRs 1 PR -80%
Design token sync effort (MindLyst) Manual (5 files) Automated (1 JSON) -100%