LetsBeBiz-Redesign/docs/architecture-proposal/claude/09-REPO-STRATEGY.md

30 KiB
Raw Permalink Blame History

LetsBe Biz — Repository Structure

Date: February 27, 2026 Team: Claude Opus 4.6 Architecture Team Document: 09 of 09 Status: Proposal — Competing with independent team


Table of Contents

  1. Decision: Monorepo
  2. Turborepo Configuration
  3. Directory Tree
  4. Package Architecture
  5. Dependency Graph
  6. Migration Plan
  7. Development Workflow
  8. Monorepo Trade-offs

1. Decision: Monorepo

Why Monorepo?

Factor Monorepo Multi-Repo Winner
Shared types Single source of truth; import directly npm publish on every change; version drift Monorepo
Atomic changes Change type + all consumers in one PR Coordinate releases across repos Monorepo
CI/CD One pipeline, matrix builds Per-repo pipelines, dependency triggering Monorepo
Code discovery grep across everything Search multiple repos separately Monorepo
Prisma schema One schema, shared by Hub and types Duplicate or publish as package Monorepo
Developer onboarding Clone one repo, npm install, done Clone 3-4 repos, configure each Monorepo
Build caching Turborepo caches across packages Each repo builds independently Monorepo
Independence Packages are more coupled Fully independent deploy Multi-Repo
Repo size Grows over time Each repo stays lean Multi-Repo
CI isolation Bad test in one package blocks others Fully isolated Multi-Repo

Decision: Monorepo with Turborepo. The shared types, Prisma schema, and tight coupling between Safety Wrapper ↔ Hub ↔ Secrets Proxy make a monorepo the clear winner. The provisioner (Bash) stays as a separate package within the monorepo but could also remain as a standalone repo if the team prefers — it has no TypeScript dependencies.

What Stays Outside the Monorepo

Component Reason
OpenClaw Upstream dependency. Pulled as Docker image. Not forked.
Tool Docker stacks Compose files and nginx configs live in the provisioner package.
Mobile app React Native/Expo has different build tooling. Lives in packages/mobile but uses its own metro.config.js.

2. Turborepo Configuration

turbo.json

{
  "$schema": "https://turbo.build/schema.json",
  "globalDependencies": ["**/.env.*local"],
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**", ".next/**"]
    },
    "typecheck": {
      "dependsOn": ["^build"]
    },
    "lint": {},
    "test": {
      "dependsOn": ["^build"],
      "env": ["DATABASE_URL", "NODE_ENV"]
    },
    "test:p0": {
      "dependsOn": ["^build"],
      "cache": false
    },
    "test:p1": {
      "dependsOn": ["^build"],
      "cache": false
    },
    "test:integration": {
      "dependsOn": ["build"],
      "cache": false
    },
    "test:benchmark": {
      "dependsOn": ["build"],
      "cache": false
    },
    "dev": {
      "cache": false,
      "persistent": true
    },
    "db:push": {
      "cache": false
    },
    "db:generate": {
      "outputs": ["node_modules/.prisma/**"]
    }
  }
}

Root package.json

{
  "name": "letsbe-biz",
  "private": true,
  "workspaces": [
    "packages/*"
  ],
  "scripts": {
    "build": "turbo run build",
    "dev": "turbo run dev --parallel",
    "test": "turbo run test",
    "test:p0": "turbo run test:p0",
    "test:integration": "turbo run test:integration",
    "lint": "turbo run lint",
    "typecheck": "turbo run typecheck",
    "format": "prettier --write \"packages/*/src/**/*.{ts,tsx}\"",
    "clean": "turbo run clean && rm -rf node_modules"
  },
  "devDependencies": {
    "turbo": "^2.3.0",
    "prettier": "^3.4.0",
    "typescript": "^5.7.0"
  },
  "engines": {
    "node": ">=22.0.0"
  }
}

3. Directory Tree

letsbe-biz/
├── .gitea/
│   └── workflows/
│       ├── ci.yml                    # Monorepo CI (lint, typecheck, test)
│       ├── safety-wrapper.yml        # SW-specific pipeline
│       ├── secrets-proxy.yml         # SP-specific pipeline
│       ├── hub.yml                   # Hub pipeline
│       ├── integration.yml           # Integration test pipeline
│       ├── deploy-staging.yml        # Auto-deploy to staging
│       ├── deploy-hub.yml            # Production Hub deploy
│       └── tenant-update.yml         # Tenant server rollout
│
├── packages/
│   ├── safety-wrapper/               # Safety Wrapper (localhost:8200)
│   │   ├── src/
│   │   │   ├── index.ts              # Entry point: HTTP server startup
│   │   │   ├── server.ts             # Express/Fastify HTTP server
│   │   │   ├── config.ts             # Configuration loading
│   │   │   ├── classification/
│   │   │   │   ├── engine.ts         # Command classification engine
│   │   │   │   ├── shell-classifier.ts   # Shell command allowlist + classification
│   │   │   │   ├── docker-classifier.ts  # Docker subcommand classification
│   │   │   │   └── rules.ts         # Classification rule definitions
│   │   │   ├── autonomy/
│   │   │   │   ├── resolver.ts       # Autonomy level resolution
│   │   │   │   ├── external-comms.ts # External Communications Gate
│   │   │   │   └── approval-queue.ts # Local approval queue (SQLite)
│   │   │   ├── executors/
│   │   │   │   ├── shell.ts          # Shell command executor (execFile)
│   │   │   │   ├── docker.ts         # Docker command executor
│   │   │   │   ├── file.ts           # File read/write executor
│   │   │   │   └── env.ts            # Env read/update executor
│   │   │   ├── secrets/
│   │   │   │   ├── registry.ts       # Encrypted SQLite secrets vault
│   │   │   │   ├── injection.ts      # SECRET_REF resolution
│   │   │   │   └── api.ts            # Secrets side-channel API
│   │   │   ├── hub/
│   │   │   │   ├── client.ts         # Hub communication (register, heartbeat, config)
│   │   │   │   └── config-sync.ts    # Config versioning and delta sync
│   │   │   ├── metering/
│   │   │   │   ├── token-tracker.ts  # Per-agent, per-model token tracking
│   │   │   │   └── bucket.ts         # Hourly bucket aggregation
│   │   │   ├── audit/
│   │   │   │   └── logger.ts         # Append-only audit log
│   │   │   └── db/
│   │   │       ├── schema.sql        # SQLite schema (secrets, approvals, audit, usage, state)
│   │   │       └── migrations/       # SQLite migration files
│   │   ├── test/
│   │   │   ├── p0/
│   │   │   │   ├── classification.test.ts  # 100+ classification tests
│   │   │   │   └── autonomy.test.ts        # Level × tier matrix tests
│   │   │   ├── p1/
│   │   │   │   ├── shell-executor.test.ts
│   │   │   │   ├── docker-executor.test.ts
│   │   │   │   └── hub-client.test.ts
│   │   │   └── integration/
│   │   │       └── openclaw-routing.test.ts
│   │   ├── Dockerfile
│   │   ├── package.json
│   │   ├── tsconfig.json
│   │   └── vitest.config.ts
│   │
│   ├── secrets-proxy/                # Secrets Proxy (localhost:8100)
│   │   ├── src/
│   │   │   ├── index.ts              # Entry point
│   │   │   ├── proxy.ts              # HTTP proxy server (transparent)
│   │   │   ├── redaction/
│   │   │   │   ├── pipeline.ts       # 4-layer pipeline orchestrator
│   │   │   │   ├── layer1-aho-corasick.ts  # Registry-based exact match
│   │   │   │   ├── layer2-regex.ts         # Pattern safety net
│   │   │   │   ├── layer3-entropy.ts       # Shannon entropy filter
│   │   │   │   └── layer4-json-keys.ts     # Sensitive key name detection
│   │   │   └── config.ts
│   │   ├── test/
│   │   │   ├── p0/
│   │   │   │   ├── redaction.test.ts       # 50+ redaction tests (TDD)
│   │   │   │   ├── false-positives.test.ts # False positive prevention
│   │   │   │   └── performance.test.ts     # <10ms latency benchmark
│   │   │   └── adversarial/
│   │   │       └── bypass-attempts.test.ts # Adversarial attack tests
│   │   ├── Dockerfile
│   │   ├── package.json
│   │   ├── tsconfig.json
│   │   └── vitest.config.ts
│   │
│   ├── hub/                          # Hub (Next.js — existing codebase, migrated)
│   │   ├── src/
│   │   │   ├── app/                  # Next.js App Router (existing structure)
│   │   │   │   ├── admin/            # Staff admin dashboard (existing)
│   │   │   │   ├── api/
│   │   │   │   │   ├── auth/         # Authentication (existing)
│   │   │   │   │   ├── v1/
│   │   │   │   │   │   ├── admin/    # Admin API (existing)
│   │   │   │   │   │   ├── tenant/   # NEW: Safety Wrapper protocol
│   │   │   │   │   │   │   ├── register/
│   │   │   │   │   │   │   ├── heartbeat/
│   │   │   │   │   │   │   ├── config/
│   │   │   │   │   │   │   ├── usage/
│   │   │   │   │   │   │   ├── approval-request/
│   │   │   │   │   │   │   └── approval-response/
│   │   │   │   │   │   ├── customer/ # NEW: Customer-facing API
│   │   │   │   │   │   │   ├── dashboard/
│   │   │   │   │   │   │   ├── agents/
│   │   │   │   │   │   │   ├── usage/
│   │   │   │   │   │   │   ├── approvals/
│   │   │   │   │   │   │   ├── billing/
│   │   │   │   │   │   │   └── tools/
│   │   │   │   │   │   ├── orchestrator/  # DEPRECATED: keep for backward compat, redirect
│   │   │   │   │   │   ├── public/    # Public API (existing)
│   │   │   │   │   │   └── webhooks/  # Stripe webhooks (existing)
│   │   │   │   │   └── cron/         # Cron endpoints (existing)
│   │   │   │   └── login/            # Login page (existing)
│   │   │   ├── lib/
│   │   │   │   ├── services/         # Business logic (existing + new)
│   │   │   │   │   ├── automation-worker.ts    # Existing
│   │   │   │   │   ├── billing-service.ts      # NEW: Token billing, Stripe Meters
│   │   │   │   │   ├── chat-relay-service.ts   # NEW: App→Hub→SW→OpenClaw
│   │   │   │   │   ├── config-generator.ts     # Existing (updated)
│   │   │   │   │   ├── push-notification.ts    # NEW: Expo Push service
│   │   │   │   │   ├── tenant-protocol.ts      # NEW: SW registration/heartbeat
│   │   │   │   │   └── ...                     # Other existing services
│   │   │   │   └── ...
│   │   │   ├── hooks/                # React Query hooks (existing)
│   │   │   └── components/           # UI components (existing)
│   │   ├── prisma/
│   │   │   ├── schema.prisma         # Shared Prisma schema (existing + new models)
│   │   │   ├── migrations/           # Prisma migrations
│   │   │   └── seed.ts               # Database seeding
│   │   ├── test/
│   │   │   ├── unit/                 # Existing unit tests (10 files)
│   │   │   ├── api/                  # NEW: API endpoint tests
│   │   │   └── integration/          # NEW: Hub↔SW protocol tests
│   │   ├── Dockerfile
│   │   ├── package.json
│   │   ├── next.config.ts
│   │   └── tsconfig.json
│   │
│   ├── website/                      # Website (letsbe.biz — separate Next.js app)
│   │   ├── src/
│   │   │   ├── app/
│   │   │   │   ├── page.tsx          # Landing page
│   │   │   │   ├── onboarding/       # AI-powered onboarding flow
│   │   │   │   │   ├── business/     # Step 1: Business description
│   │   │   │   │   ├── tools/        # Step 2: Tool recommendation
│   │   │   │   │   ├── customize/    # Step 3: Customization
│   │   │   │   │   ├── server/       # Step 4: Server selection
│   │   │   │   │   ├── domain/       # Step 5: Domain setup
│   │   │   │   │   ├── agents/       # Step 6: Agent config (optional)
│   │   │   │   │   ├── payment/      # Step 7: Stripe checkout
│   │   │   │   │   └── status/       # Step 8: Provisioning status
│   │   │   │   ├── demo/             # Interactive demo page
│   │   │   │   └── pricing/          # Pricing page
│   │   │   └── lib/
│   │   │       ├── ai-classifier.ts  # Gemini Flash business classifier
│   │   │       └── resource-calc.ts  # Resource requirement calculator
│   │   ├── Dockerfile
│   │   ├── package.json
│   │   └── tsconfig.json
│   │
│   ├── mobile/                       # Mobile App (Expo Bare Workflow)
│   │   ├── src/
│   │   │   ├── screens/
│   │   │   │   ├── LoginScreen.tsx
│   │   │   │   ├── ChatScreen.tsx
│   │   │   │   ├── DashboardScreen.tsx
│   │   │   │   ├── ApprovalsScreen.tsx
│   │   │   │   ├── UsageScreen.tsx
│   │   │   │   ├── SettingsScreen.tsx
│   │   │   │   └── SecretsScreen.tsx
│   │   │   ├── components/
│   │   │   ├── hooks/
│   │   │   ├── stores/               # Zustand stores
│   │   │   ├── services/             # API client, push notifications
│   │   │   └── navigation/           # React Navigation
│   │   ├── app.json
│   │   ├── eas.json                  # EAS Build + Update config
│   │   ├── metro.config.js
│   │   ├── package.json
│   │   └── tsconfig.json
│   │
│   ├── shared-types/                 # Shared TypeScript types
│   │   ├── src/
│   │   │   ├── classification.ts     # Command classification types
│   │   │   ├── autonomy.ts           # Autonomy level types
│   │   │   ├── secrets.ts            # Secrets registry types
│   │   │   ├── protocol.ts           # Hub ↔ SW protocol types
│   │   │   ├── billing.ts            # Token metering types
│   │   │   ├── agents.ts             # Agent configuration types
│   │   │   └── index.ts              # Barrel export
│   │   ├── package.json
│   │   └── tsconfig.json
│   │
│   ├── shared-prisma/                # Shared Prisma client (generated)
│   │   ├── prisma/
│   │   │   └── schema.prisma         # → symlink to packages/hub/prisma/schema.prisma
│   │   ├── package.json
│   │   └── tsconfig.json
│   │
│   └── provisioner/                  # Provisioner (Bash — migrated from letsbe-ansible-runner)
│       ├── provision.sh              # Main entry point
│       ├── steps/
│       │   ├── step-01-system-update.sh
│       │   ├── step-02-docker-install.sh
│       │   ├── step-03-create-user.sh
│       │   ├── step-04-generate-secrets.sh
│       │   ├── step-05-deploy-stacks.sh
│       │   ├── step-06-nginx-configs.sh
│       │   ├── step-07-ssl-certs.sh
│       │   ├── step-08-backup-setup.sh
│       │   ├── step-09-firewall.sh
│       │   └── step-10-deploy-ai.sh      # REWRITTEN: OpenClaw + Safety Wrapper
│       ├── stacks/                   # Docker Compose files for 28+ tools
│       │   ├── chatwoot/
│       │   │   └── docker-compose.yml
│       │   ├── nextcloud/
│       │   │   └── docker-compose.yml
│       │   ├── letsbe/               # NEW: LetsBe AI stack
│       │   │   └── docker-compose.yml  # OpenClaw + Safety Wrapper + Secrets Proxy
│       │   └── ...
│       ├── nginx/                    # nginx configs for 33+ tools
│       ├── templates/                # Config templates
│       │   ├── openclaw-config.json5.tmpl
│       │   ├── safety-wrapper.json.tmpl
│       │   ├── tool-registry.json.tmpl
│       │   └── agent-templates/      # Per-business-type agent configs
│       ├── references/               # Tool cheat sheets (deployed to tenant)
│       │   ├── portainer.md
│       │   ├── nextcloud.md
│       │   ├── chatwoot.md
│       │   ├── ghost.md
│       │   ├── calcom.md
│       │   ├── stalwart.md
│       │   └── ...
│       ├── skills/                   # OpenClaw skills (deployed to tenant)
│       │   └── letsbe-tools/
│       │       └── SKILL.md          # Master tool skill
│       ├── agents/                   # Default agent configs (deployed to tenant)
│       │   ├── dispatcher/
│       │   │   └── SOUL.md
│       │   ├── it-admin/
│       │   │   └── SOUL.md
│       │   ├── marketing/
│       │   │   └── SOUL.md
│       │   ├── secretary/
│       │   │   └── SOUL.md
│       │   └── sales/
│       │       └── SOUL.md
│       ├── test/
│       │   ├── step-10.bats          # bats-core tests for step 10
│       │   ├── cleanup.bats          # n8n cleanup verification
│       │   └── full-run.bats         # Full provisioner integration test
│       ├── Dockerfile
│       └── package.json              # Minimal — just for monorepo workspace inclusion
│
├── test/                             # Cross-package integration tests
│   ├── docker-compose.integration.yml  # Full stack for integration tests
│   ├── fixtures/
│   │   ├── openclaw-config.json5
│   │   ├── safety-wrapper-config.json
│   │   ├── tool-registry.json
│   │   └── test-secrets.json
│   └── e2e/
│       ├── signup-to-chat.test.ts
│       ├── approval-flow.test.ts
│       └── secrets-never-leak.test.ts
│
├── docs/                             # Documentation (existing)
│   ├── technical/
│   ├── strategy/
│   ├── legal/
│   └── architecture-proposal/
│       └── claude/                   # This proposal
│
├── turbo.json
├── package.json                      # Root workspace config
├── tsconfig.base.json                # Shared TypeScript config
├── .gitignore
├── .eslintrc.js                      # Shared ESLint config
├── .prettierrc
└── README.md

4. Package Architecture

Package Responsibilities

Package Language Purpose Depends On Deployed As
safety-wrapper TypeScript Command gating, tool execution, Hub comm, audit shared-types Docker container on tenant VPS
secrets-proxy TypeScript LLM traffic redaction (4-layer pipeline) shared-types Docker container on tenant VPS
hub TypeScript (Next.js) Admin dashboard, customer portal, billing, tenant protocol shared-types, shared-prisma Docker container on central server
website TypeScript (Next.js) Marketing site, onboarding flow Docker container on central server
mobile TypeScript (Expo) Customer mobile app shared-types iOS/Android app (EAS Build)
shared-types TypeScript Type definitions shared across packages npm workspace dependency
shared-prisma TypeScript Generated Prisma client npm workspace dependency
provisioner Bash VPS provisioning scripts, tool stacks Docker container (on-demand)

Package Size Estimates

Package Estimated LOC Files Build Output
safety-wrapper ~3,000-4,000 ~30 ~200KB JS
secrets-proxy ~1,500-2,000 ~15 ~100KB JS
hub ~15,000+ (existing) + ~3,000 new ~250+ Next.js standalone
website ~2,000-3,000 ~20 Next.js standalone
mobile ~4,000-5,000 ~40 Expo bundle
shared-types ~500-800 ~10 ~50KB JS
provisioner ~5,000 (existing + new) ~50+ Bash scripts

5. Dependency Graph

                    ┌──────────────┐
                    │ shared-types │
                    └──────┬───────┘
              ┌────────────┼────────────┬────────────┐
              │            │            │            │
     ┌────────▼──────┐ ┌──▼────────┐ ┌─▼──────┐ ┌──▼──────┐
     │safety-wrapper │ │secrets-   │ │  hub   │ │ mobile  │
     │               │ │proxy      │ │        │ │         │
     └───────────────┘ └───────────┘ └────┬───┘ └─────────┘
                                          │
                                   ┌──────▼──────┐
                                   │shared-prisma│
                                   └─────────────┘

    ┌───────────┐     ┌───────────┐
    │  website  │     │provisioner│
    │(no deps)  │     │(Bash, no  │
    │           │     │ TS deps)  │
    └───────────┘     └───────────┘

Key constraints:

  • shared-types has ZERO dependencies. It's pure TypeScript type definitions.
  • shared-prisma depends only on Prisma and the schema file.
  • safety-wrapper and secrets-proxy never import from hub (no circular deps).
  • hub never imports from safety-wrapper or secrets-proxy (communication via HTTP protocol).
  • website is fully independent — no shared package dependencies.
  • provisioner is Bash — no TypeScript dependencies at all.

6. Migration Plan

Current State (5 Separate Repos)

letsbe-hub           → packages/hub (TypeScript, Next.js)
letsbe-ansible-runner → packages/provisioner (Bash)
letsbe-orchestrator   → DEPRECATED (capabilities → safety-wrapper)
letsbe-sysadmin-agent → DEPRECATED (capabilities → safety-wrapper)
letsbe-mcp-browser    → DEPRECATED (replaced by OpenClaw native browser)

Migration Steps

Step 1: Create Monorepo (Week 1, Day 1-2)

# Create new repo
mkdir letsbe-biz && cd letsbe-biz
git init
npm init -y

# Install Turborepo
npm install turbo --save-dev

# Create workspace structure
mkdir -p packages/{safety-wrapper,secrets-proxy,hub,website,mobile,shared-types,shared-prisma,provisioner}

# Create turbo.json (from Section 2)
# Create root package.json (from Section 2)
# Create tsconfig.base.json

Step 2: Migrate Hub (Week 1, Day 1)

# Copy Hub source (preserve git history via subtree or fresh copy)
cp -r ../letsbe-hub/src packages/hub/src
cp -r ../letsbe-hub/prisma packages/hub/prisma
cp ../letsbe-hub/package.json packages/hub/
cp ../letsbe-hub/next.config.ts packages/hub/
cp ../letsbe-hub/tsconfig.json packages/hub/
cp ../letsbe-hub/Dockerfile packages/hub/

# Update Hub package.json:
# - name: "@letsbe/hub"
# - Add workspace dependency on shared-types, shared-prisma

# Verify Hub builds
cd packages/hub && npm install && npm run build

Step 3: Migrate Provisioner (Week 1, Day 1)

# Copy provisioner scripts
cp -r ../letsbe-ansible-runner/* packages/provisioner/

# Add minimal package.json for workspace inclusion
echo '{"name":"@letsbe/provisioner","private":true}' > packages/provisioner/package.json

Step 4: Create New Packages (Week 1, Day 2)

# shared-types — create from scratch
cd packages/shared-types
npm init -y --scope=@letsbe
# Add type definitions

# safety-wrapper — create from scratch
cd packages/safety-wrapper
npm init -y --scope=@letsbe
# Scaffold Express/Fastify server

# secrets-proxy — create from scratch
cd packages/secrets-proxy
npm init -y --scope=@letsbe
# Scaffold HTTP proxy

Step 5: Verify Everything Works (Week 1, Day 2)

# From repo root:
npm install               # Install all workspace dependencies
turbo run build            # Build all packages
turbo run typecheck        # Type check all packages
turbo run test             # Run all tests (Hub's existing 10 tests)
turbo run lint             # Lint all packages

Step 6: Archive Old Repos (Week 2)

Once the monorepo is confirmed working and the team has switched:

  1. Mark letsbe-orchestrator as archived (deprecated)
  2. Mark letsbe-sysadmin-agent as archived (deprecated)
  3. Mark letsbe-mcp-browser as archived (deprecated)
  4. Keep letsbe-hub and letsbe-ansible-runner read-only for reference
  5. Update Gitea CI to point to new monorepo

Git History Preservation

Option A (Recommended): Fresh start with reference.

  • New monorepo gets a clean git history.
  • Old repos remain accessible (read-only archive) for historical reference.
  • This is cleaner and avoids complex git subtree merges.

Option B: Preserve history via git subtree.

  • Use git subtree add to bring Hub and provisioner history into the monorepo.
  • More complex but preserves git blame lineage.

Recommendation: Option A. The codebase is being substantially restructured. Historical blame on the old code is less valuable than a clean starting point. The old repos stay available for reference.


7. Development Workflow

Daily Development

# Start all dev servers (Hub + Safety Wrapper + Secrets Proxy)
turbo run dev --parallel

# Run tests for a specific package
turbo run test --filter=safety-wrapper

# Run P0 tests only
turbo run test:p0

# Build a specific package
turbo run build --filter=secrets-proxy

# Type check everything
turbo run typecheck

# Lint everything
turbo run lint

Adding a Shared Type

# 1. Add type to packages/shared-types/src/classification.ts
# 2. Export from index.ts
# 3. Import in consuming package:
#    import { CommandTier } from '@letsbe/shared-types';
# 4. Turbo automatically rebuilds shared-types before dependent packages

Adding a New Package

# 1. Create directory
mkdir packages/new-package

# 2. Initialize
cd packages/new-package
npm init -y --scope=@letsbe

# 3. Add to root workspaces (already covered by packages/* glob)

# 4. Add to turbo.json pipeline if needed

# 5. Add Dockerfile if it's a deployed service

Docker Development

# docker-compose.dev.yml (root level, for local development)
services:
  postgres:
    image: postgres:16-alpine
    ports: ['5432:5432']
    environment:
      POSTGRES_DB: hub_dev
      POSTGRES_USER: hub
      POSTGRES_PASSWORD: devpass

  hub:
    build:
      context: .
      dockerfile: packages/hub/Dockerfile
    ports: ['3000:3000']
    environment:
      DATABASE_URL: postgresql://hub:devpass@postgres:5432/hub_dev
    depends_on: [postgres]

  safety-wrapper:
    build:
      context: .
      dockerfile: packages/safety-wrapper/Dockerfile
    ports: ['8200:8200']

  secrets-proxy:
    build:
      context: .
      dockerfile: packages/secrets-proxy/Dockerfile
    ports: ['8100:8100']

8. Monorepo Trade-offs

Advantages Realized

Advantage Concrete Benefit
Atomic type changes Change CommandTier enum in shared-types → all consumers updated in same PR
Turborepo caching Rebuild only changed packages; CI runs ~60% faster after first run
Shared tooling One ESLint config, one Prettier config, one TypeScript base config
Cross-package refactoring Rename a protocol field → update Safety Wrapper + Hub in one commit
Single dependency tree No version conflicts between packages; hoisted node_modules
Simplified onboarding Clone one repo → npm installturbo run dev → everything running

Disadvantages Accepted

Disadvantage Mitigation
Larger repo size Turborepo's --filter flag runs only affected packages
Bash in TypeScript monorepo Provisioner is loosely coupled — workspace inclusion is just for organization
Mobile build complexity Expo has its own build system (EAS); it coexists but doesn't use Turbo for builds
CI runs all checks Path-based triggers (see pipeline YAML) skip unrelated packages
Single repo = single SPOF Gitea backup strategy; consider GitHub mirror for disaster recovery

When to Reconsider

The monorepo should be split if:

  • The team grows beyond 8-10 engineers and package ownership boundaries become clear
  • Mobile app development cadence diverges significantly from backend
  • A package needs a fundamentally different build system or language (e.g., Rust Safety Wrapper rewrite)
  • CI times exceed 20 minutes even with caching

None of these are likely before reaching 100 customers.


End of Document — 09 Repository Structure