# LetsBe Biz — CI/CD Strategy **Date:** February 27, 2026 **Team:** Claude Opus 4.6 Architecture Team **Document:** 08 of 09 **Status:** Proposal — Competing with independent team --- ## Table of Contents 1. [CI/CD Overview](#1-cicd-overview) 2. [Gitea Actions Pipelines](#2-gitea-actions-pipelines) 3. [Branch Strategy](#3-branch-strategy) 4. [Build & Publish](#4-build--publish) 5. [Deployment Workflows](#5-deployment-workflows) 6. [Rollback Procedures](#6-rollback-procedures) 7. [Secret Management in CI](#7-secret-management-in-ci) 8. [Quality Gates in CI](#8-quality-gates-in-ci) 9. [Monitoring & Alerting](#9-monitoring--alerting) --- ## 1. CI/CD Overview ### Platform: Gitea Actions Gitea Actions is the CI/CD platform (Architecture Brief §9.1). It uses GitHub Actions-compatible YAML workflow syntax, making migration straightforward if needed later. ### Pipeline Architecture ``` Developer pushes code │ ▼ ┌──────────────────┐ │ Gitea Actions │ │ Trigger: push │ │ │ │ 1. Lint │ │ 2. Type Check │ │ 3. Unit Tests │ │ 4. Build │ │ 5. Security Scan │ └────────┬─────────┘ │ ┌────┴────┐ │ Branch? │ └────┬────┘ │ ┌────┼────────────┐ │ │ │ feature develop main │ │ │ │ ▼ ▼ │ Build Docker Build Docker │ Push :dev Push :latest │ │ │ │ ▼ ▼ │ Deploy to Deploy to │ staging production │ │ │ ▼ │ Canary rollout │ (tenant servers) │ └─► PR required to merge ``` ### Environments | Environment | Branch | Trigger | Purpose | |-------------|--------|---------|---------| | **Local** | Any | Manual | Developer testing | | **CI** | Any push | Automatic | Lint, test, type check | | **Staging** | `develop` | Automatic on merge | Integration testing, dogfooding | | **Production** | `main` | Manual approval | Live customers | --- ## 2. Gitea Actions Pipelines ### 2.1 Monorepo CI Pipeline (All Packages) ```yaml # .gitea/workflows/ci.yml name: CI on: push: branches: [main, develop, 'feature/**'] pull_request: branches: [main, develop] env: NODE_VERSION: '22' jobs: lint-and-typecheck: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} - name: Install dependencies run: npm ci - name: Lint run: npx turbo run lint - name: Type check run: npx turbo run typecheck unit-tests: runs-on: ubuntu-latest needs: lint-and-typecheck strategy: matrix: package: - safety-wrapper - secrets-proxy - hub - shared-types steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} - name: Install dependencies run: npm ci - name: Run tests for ${{ matrix.package }} run: npx turbo run test --filter=${{ matrix.package }} security-scan: runs-on: ubuntu-latest needs: lint-and-typecheck steps: - uses: actions/checkout@v4 - name: Check for secrets in code run: | npx @trufflesecurity/trufflehog git file://. --only-verified --fail - name: Dependency audit run: npm audit --audit-level=high ``` ### 2.2 Safety Wrapper Pipeline ```yaml # .gitea/workflows/safety-wrapper.yml name: Safety Wrapper on: push: paths: - 'packages/safety-wrapper/**' - 'packages/shared-types/**' branches: [main, develop] jobs: p0-tests: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: '22' - run: npm ci - name: P0 Secrets Redaction Tests run: npx turbo run test:p0 --filter=secrets-proxy - name: P0 Command Classification Tests run: npx turbo run test:p0 --filter=safety-wrapper - name: P1 Autonomy Tests run: npx turbo run test:p1 --filter=safety-wrapper build-image: runs-on: ubuntu-latest needs: p0-tests if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop' steps: - uses: actions/checkout@v4 - name: Set tag id: tag run: | if [ "${{ github.ref }}" = "refs/heads/main" ]; then echo "tag=latest" >> $GITHUB_OUTPUT else echo "tag=dev" >> $GITHUB_OUTPUT fi - name: Build Safety Wrapper image run: | docker build \ -f packages/safety-wrapper/Dockerfile \ -t code.letsbe.solutions/letsbe/safety-wrapper:${{ steps.tag.outputs.tag }} \ -t code.letsbe.solutions/letsbe/safety-wrapper:${{ github.sha }} \ . - name: Push to registry run: | echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login code.letsbe.solutions -u ${{ secrets.REGISTRY_USER }} --password-stdin docker push code.letsbe.solutions/letsbe/safety-wrapper:${{ steps.tag.outputs.tag }} docker push code.letsbe.solutions/letsbe/safety-wrapper:${{ github.sha }} ``` ### 2.3 Secrets Proxy Pipeline ```yaml # .gitea/workflows/secrets-proxy.yml name: Secrets Proxy on: push: paths: - 'packages/secrets-proxy/**' - 'packages/shared-types/**' branches: [main, develop] jobs: p0-tests: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: { node-version: '22' } - run: npm ci - name: P0 Redaction Tests (must pass 100%) run: npx turbo run test:p0 --filter=secrets-proxy - name: Performance Benchmark run: npx turbo run test:benchmark --filter=secrets-proxy build-image: runs-on: ubuntu-latest needs: p0-tests if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop' steps: - uses: actions/checkout@v4 - name: Build Secrets Proxy image run: | docker build \ -f packages/secrets-proxy/Dockerfile \ -t code.letsbe.solutions/letsbe/secrets-proxy:${{ github.ref == 'refs/heads/main' && 'latest' || 'dev' }} \ . - name: Push to registry run: | echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login code.letsbe.solutions -u ${{ secrets.REGISTRY_USER }} --password-stdin docker push code.letsbe.solutions/letsbe/secrets-proxy --all-tags ``` ### 2.4 Hub Pipeline ```yaml # .gitea/workflows/hub.yml name: Hub on: push: paths: - 'packages/hub/**' - 'packages/shared-prisma/**' branches: [main, develop] jobs: test: runs-on: ubuntu-latest services: postgres: image: postgres:16-alpine env: POSTGRES_DB: hub_test POSTGRES_USER: hub POSTGRES_PASSWORD: testpass ports: ['5432:5432'] steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: { node-version: '22' } - run: npm ci - name: Run Prisma migrations run: npx turbo run db:push --filter=hub env: DATABASE_URL: postgresql://hub:testpass@localhost:5432/hub_test - name: Run tests run: npx turbo run test --filter=hub env: DATABASE_URL: postgresql://hub:testpass@localhost:5432/hub_test build-image: runs-on: ubuntu-latest needs: test if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop' steps: - uses: actions/checkout@v4 - name: Build Hub image run: | docker build \ -f packages/hub/Dockerfile \ -t code.letsbe.solutions/letsbe/hub:${{ github.ref == 'refs/heads/main' && 'latest' || 'dev' }} \ . - name: Push to registry run: | echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login code.letsbe.solutions -u ${{ secrets.REGISTRY_USER }} --password-stdin docker push code.letsbe.solutions/letsbe/hub --all-tags ``` ### 2.5 Integration Test Pipeline ```yaml # .gitea/workflows/integration.yml name: Integration Tests on: push: branches: [develop] workflow_dispatch: jobs: integration: runs-on: ubuntu-latest timeout-minutes: 30 steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: { node-version: '22' } - run: npm ci - name: Start integration stack run: docker compose -f test/docker-compose.integration.yml up -d --wait timeout-minutes: 5 - name: Wait for services run: | for i in $(seq 1 30); do curl -sf http://localhost:8200/health && break || sleep 2 done - name: Run integration tests run: npx turbo run test:integration - name: Collect logs on failure if: failure() run: docker compose -f test/docker-compose.integration.yml logs > integration-logs.txt - name: Upload logs if: failure() uses: actions/upload-artifact@v4 with: name: integration-logs path: integration-logs.txt - name: Teardown if: always() run: docker compose -f test/docker-compose.integration.yml down -v ``` --- ## 3. Branch Strategy ### Git Flow (Simplified) ``` main ─────────────────────────────────────────────────► │ ▲ │ │ (merge via PR, requires approval) │ │ develop ──┬───────────┬───────────┬────────┤ │ │ │ feature/sw-skeleton │ feature/hub-billing │ │ │ feature/secrets-proxy │ hotfix/critical-fix ──────────────────────► main (direct merge for critical fixes) ``` ### Branch Rules | Branch | Protection | Merge Requirements | |--------|-----------|-------------------| | `main` | Protected; no direct pushes | PR from `develop`; 1 approval; all CI checks pass; security scan pass | | `develop` | Protected; no direct pushes | PR from feature branch; all CI checks pass | | `feature/*` | Unprotected | Free to push; PR to develop when ready | | `hotfix/*` | Unprotected | Can merge to both `main` and `develop`; 1 approval required | ### Naming Conventions ``` feature/sw-command-classification # Safety Wrapper feature feature/hub-tenant-api # Hub feature feature/mobile-chat-view # Mobile app feature feature/prov-step10-rewrite # Provisioner feature fix/secrets-proxy-jwt-detection # Bug fix hotfix/redaction-bypass-cve # Critical security fix ``` ### Release Tagging ``` v0.1.0 # First internal milestone (M1) v0.2.0 # M2 v0.3.0 # M3 v1.0.0 # Founding member launch (M4) v1.0.1 # First patch v1.1.0 # First feature update post-launch ``` --- ## 4. Build & Publish ### Docker Image Strategy | Image | Registry Path | Build Context | Size Target | |-------|--------------|---------------|-------------| | `letsbe/safety-wrapper` | `code.letsbe.solutions/letsbe/safety-wrapper` | `packages/safety-wrapper/` | <150MB | | `letsbe/secrets-proxy` | `code.letsbe.solutions/letsbe/secrets-proxy` | `packages/secrets-proxy/` | <100MB | | `letsbe/hub` | `code.letsbe.solutions/letsbe/hub` | `packages/hub/` | <500MB | | `letsbe/ansible-runner` | `code.letsbe.solutions/letsbe/ansible-runner` | `packages/provisioner/` | Existing | ### Multi-Stage Dockerfile Pattern ```dockerfile # packages/safety-wrapper/Dockerfile # Stage 1: Dependencies FROM node:22-alpine AS deps WORKDIR /app COPY package.json package-lock.json ./ COPY packages/safety-wrapper/package.json ./packages/safety-wrapper/ COPY packages/shared-types/package.json ./packages/shared-types/ RUN npm ci --workspace=packages/safety-wrapper --workspace=packages/shared-types # Stage 2: Build FROM node:22-alpine AS builder WORKDIR /app COPY --from=deps /app/node_modules ./node_modules COPY packages/safety-wrapper/ ./packages/safety-wrapper/ COPY packages/shared-types/ ./packages/shared-types/ COPY turbo.json package.json ./ RUN npx turbo run build --filter=safety-wrapper # Stage 3: Production FROM node:22-alpine AS runner WORKDIR /app RUN addgroup -g 1001 -S letsbe && adduser -S letsbe -u 1001 COPY --from=builder /app/packages/safety-wrapper/dist ./dist COPY --from=builder /app/packages/safety-wrapper/package.json ./ COPY --from=deps /app/node_modules ./node_modules USER letsbe EXPOSE 8200 CMD ["node", "dist/index.js"] ``` ### Image Tagging | Tag | When | Purpose | |-----|------|---------| | `:dev` | On merge to `develop` | Staging deployment | | `:latest` | On merge to `main` | Production deployment | | `:` | On every build | Immutable reference for debugging | | `:v1.0.0` | On release tag | Version-pinned deployment | --- ## 5. Deployment Workflows ### 5.1 Central Platform (Hub) Deployment ```yaml # .gitea/workflows/deploy-hub.yml name: Deploy Hub on: push: branches: [main] paths: ['packages/hub/**', 'packages/shared-prisma/**'] jobs: deploy: runs-on: ubuntu-latest environment: production steps: - name: Deploy to production run: | ssh -o StrictHostKeyChecking=no deploy@hub.letsbe.biz << 'EOF' cd /opt/letsbe/hub docker compose pull hub docker compose up -d hub # Wait for health check for i in $(seq 1 30); do curl -sf http://localhost:3847/api/health && break || sleep 2 done # Run migrations docker compose exec hub npx prisma migrate deploy EOF ``` ### 5.2 Tenant Server Update Pipeline Tenant servers are updated via the Hub push mechanism (see 03-DEPLOYMENT-STRATEGY §7). ```yaml # .gitea/workflows/tenant-update.yml name: Tenant Server Update on: workflow_dispatch: inputs: component: description: 'Component to update' required: true type: choice options: [safety-wrapper, secrets-proxy, openclaw] strategy: description: 'Rollout strategy' required: true type: choice options: [staging-only, canary-5pct, canary-25pct, full-rollout] jobs: prepare: runs-on: ubuntu-latest steps: - name: Verify image exists run: | docker manifest inspect code.letsbe.solutions/letsbe/${{ inputs.component }}:latest rollout: runs-on: ubuntu-latest needs: prepare steps: - name: Trigger Hub rollout API run: | curl -X POST https://hub.letsbe.biz/api/v1/admin/rollout \ -H "Authorization: Bearer ${{ secrets.HUB_ADMIN_TOKEN }}" \ -H "Content-Type: application/json" \ -d '{ "component": "${{ inputs.component }}", "tag": "latest", "strategy": "${{ inputs.strategy }}" }' ``` ### 5.3 Staging Deployment (Automatic) ```yaml # .gitea/workflows/deploy-staging.yml name: Deploy Staging on: push: branches: [develop] jobs: deploy-staging: runs-on: ubuntu-latest environment: staging steps: - name: Deploy Hub to staging run: | ssh deploy@staging.letsbe.biz << 'EOF' cd /opt/letsbe/hub docker compose pull docker compose up -d docker compose exec hub npx prisma migrate deploy EOF - name: Deploy tenant stack to staging VPS run: | ssh deploy@staging-tenant.letsbe.biz << 'EOF' cd /opt/letsbe docker compose -f docker-compose.letsbe.yml pull docker compose -f docker-compose.letsbe.yml up -d EOF - name: Run smoke tests run: | curl -sf https://staging.letsbe.biz/api/health curl -sf https://staging-tenant.letsbe.biz:8200/health curl -sf https://staging-tenant.letsbe.biz:8100/health ``` --- ## 6. Rollback Procedures ### 6.1 Hub Rollback ```bash # Rollback Hub to previous version ssh deploy@hub.letsbe.biz << 'EOF' cd /opt/letsbe/hub # Find previous image PREVIOUS=$(docker compose images hub --format '{{.Tag}}' | head -1) # Pull and deploy previous docker compose pull hub # Uses previous :latest from registry docker compose up -d hub # Verify health for i in $(seq 1 30); do curl -sf http://localhost:3847/api/health && break || sleep 2 done # Note: Prisma migrations are forward-only. # If a migration needs reverting, use prisma migrate resolve. EOF ``` ### 6.2 Tenant Component Rollback ```bash # Rollback Safety Wrapper on a specific tenant ssh deploy@tenant-server << 'EOF' cd /opt/letsbe # Roll back to pinned SHA docker compose -f docker-compose.letsbe.yml \ -e SAFETY_WRAPPER_TAG= \ up -d safety-wrapper # Verify health curl -sf http://127.0.0.1:8200/health EOF ``` ### 6.3 Rollback Decision Matrix | Symptom | Action | Automatic? | |---------|--------|-----------| | Health check fails after deploy | Rollback to previous image | Yes (Docker restart policy pulls previous on repeated failure) | | P0 tests fail in CI | Block merge; no deployment | Yes (CI gate) | | Secrets redaction miss detected | EMERGENCY: rollback all tenants immediately | Manual (requires admin trigger) | | Hub API errors >5% | Rollback Hub to previous version | Manual (monitoring alert) | | Billing discrepancy | Investigate first; rollback billing code if confirmed | Manual | ### 6.4 Emergency Rollback Checklist For critical security issues (e.g., redaction bypass): 1. **STOP** all tenant updates immediately (disable Hub rollout API) 2. **ROLLBACK** all affected components to last known-good version 3. **VERIFY** rollback successful (health checks, P0 tests) 4. **INVESTIGATE** root cause 5. **FIX** and add test case for the specific failure 6. **AUDIT** all tenants for potential exposure during the window 7. **NOTIFY** affected customers if secrets were potentially exposed 8. **POST-MORTEM** within 24 hours --- ## 7. Secret Management in CI ### Gitea Secrets Configuration | Secret | Scope | Purpose | |--------|-------|---------| | `REGISTRY_USER` | Organization | Docker registry login | | `REGISTRY_PASSWORD` | Organization | Docker registry password | | `HUB_ADMIN_TOKEN` | Repository | Hub API authentication for deployments | | `STAGING_SSH_KEY` | Repository | SSH key for staging deployment | | `PRODUCTION_SSH_KEY` | Repository | SSH key for production deployment | | `STRIPE_TEST_KEY` | Repository | Stripe test mode for integration tests | ### Rules 1. **Never** put secrets in workflow YAML files 2. **Never** echo secrets in CI logs (use `::add-mask::`) 3. **Never** pass secrets as command-line arguments (use environment variables) 4. SSH keys: use deploy keys with minimal permissions (read-only for CI, write for deploy) 5. Rotate all CI secrets quarterly --- ## 8. Quality Gates in CI ### Gate Configuration ```yaml # In each pipeline, quality gates are enforced as job dependencies: jobs: # Gate 1: Code quality lint: # Must pass before tests run ... typecheck: # Must pass before tests run ... # Gate 2: Correctness unit-tests: needs: [lint, typecheck] # Must pass before build ... # Gate 3: Security security-scan: needs: [lint] # Must pass before deploy ... # Gate 4: Build build: needs: [unit-tests, security-scan] # Must succeed before deploy ... # Gate 5: Deploy (only on protected branches) deploy: needs: [build] if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop' ... ``` ### PR Merge Requirements | Requirement | Enforcement | |-------------|------------| | All CI checks pass | Gitea branch protection rule | | At least 1 approval | Gitea branch protection rule | | No unresolved review comments | Convention (not enforced by Gitea) | | P0 tests pass if security code changed | CI pipeline condition | | No secrets detected in diff | trufflehog scan | --- ## 9. Monitoring & Alerting ### CI Pipeline Monitoring | Metric | Alert Threshold | Action | |--------|----------------|--------| | Build duration | >15 min | Investigate; optimize caching | | Test suite duration | >10 min | Investigate; parallelize tests | | Failed builds on `develop` | >3 consecutive | Freeze merges; investigate | | Failed deploys | Any | Automatic rollback; notify team | | Security scan findings | Any critical | Block merge; assign to Security Lead | ### Deployment Monitoring | Metric | Alert Threshold | Action | |--------|----------------|--------| | Hub health after deploy | Unhealthy for >60s | Automatic rollback | | Tenant health after update | Unhealthy for >120s | Rollback specific tenant; pause rollout | | Error rate post-deploy | >5% increase | Alert team; investigate | | Latency post-deploy | >2× baseline | Alert team; investigate | ### Notification Channels | Event | Channel | |-------|---------| | CI failure on `main` | Team chat (immediate) | | Security scan finding | Team chat + email to Security Lead | | Deployment success | Team chat (informational) | | Deployment failure | Team chat + email to on-call | | Emergency rollback | Team chat + phone call to on-call | --- *End of Document — 08 CI/CD Strategy*