mirror of
https://github.com/blackboxprogramming/BlackRoad-Operating-System.git
synced 2026-03-17 03:57:13 -05:00
This implements the "Automate The Company" initiative with comprehensive
Standard Operating Procedures for GitHub + Salesforce + Asana integration.
New directory: sop/
├── workflows/ - End-to-end process documentation
│ ├── new-client-kickoff.md - Flagship workflow from deal → repos → Asana
│ └── release-pipeline.md - Deploy → update Salesforce + Asana
├── playbooks/ - Human-friendly checklists
│ └── brenda-new-client-checklist.md - Non-technical operator guide
├── salesforce/ - Salesforce automation specifications
│ ├── flows/opp-automation-onstagechange.md - Trigger on Closed Won
│ └── orchestrations/new-client-kickoff-orchestration.md - Multi-stage process
├── integrations/ - API integration specifications
│ ├── salesforce-to-github.md - Create repos from Salesforce
│ ├── github-to-salesforce.md - Update Salesforce after deploy
│ └── salesforce-to-asana.md - Create Asana projects from Salesforce
└── templates/ - Reusable templates
├── github-actions/ - CI/CD workflows (ci.yml, deploy.yml, safety.yml)
└── repo-template/ - Standard repo config (PR template, labels, branch protection)
Key Features:
- Event-driven automation (Closed Won → repos + Asana creation)
- GitHub Actions templates for CI/CD baseline
- Salesforce Flow & Orchestration specs
- Complete API integration documentation
- Operator-friendly playbooks
- Two-view approach (operator + engineer)
- No manual status syncing across systems
This provides the complete backbone for next-gen ERP automation.
557 lines
15 KiB
Markdown
557 lines
15 KiB
Markdown
# Release Pipeline Workflow
|
|
|
|
**Owner:** Engineering + DevOps
|
|
**Systems:** GitHub → Railway/Cloudflare → Salesforce → Asana
|
|
**Status:** Active
|
|
**Last Updated:** 2025-11-17
|
|
|
|
---
|
|
|
|
## Overview
|
|
|
|
This workflow automates the **entire release and deployment lifecycle**, from code commit to production deployment to updating all stakeholders automatically.
|
|
|
|
**Key Principle:** Merge to `main` should feel like magic. Everything else happens automatically.
|
|
|
|
---
|
|
|
|
## The Pipeline
|
|
|
|
```
|
|
Developer pushes code
|
|
↓
|
|
GitHub Actions: CI pipeline (test + lint + build)
|
|
↓
|
|
PASS → Ready for PR
|
|
↓
|
|
PR created + reviewed + approved
|
|
↓
|
|
PR merged to `main`
|
|
↓
|
|
GitHub Actions: Deploy pipeline
|
|
↓
|
|
├─→ Deploy to Railway (backend)
|
|
├─→ Deploy to Cloudflare Pages (frontend)
|
|
└─→ Update configs (ops)
|
|
↓
|
|
Deploy succeeds
|
|
↓
|
|
GitHub Actions: Notify stakeholders
|
|
↓
|
|
├─→ Update Salesforce Project (deploy metadata)
|
|
├─→ Complete Asana tasks (deployment checklist)
|
|
└─→ Post to Slack #deploys
|
|
```
|
|
|
|
---
|
|
|
|
## Stage 1: Continuous Integration (CI)
|
|
|
|
**Trigger:**
|
|
- Every push to any branch
|
|
- Every pull request opened/updated
|
|
|
|
**GitHub Action:** `.github/workflows/ci.yml`
|
|
|
|
**Jobs:**
|
|
|
|
### 1.1 Test
|
|
```yaml
|
|
test:
|
|
runs-on: ubuntu-latest
|
|
strategy:
|
|
matrix:
|
|
python-version: [3.11, 3.12]
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- name: Set up Python
|
|
uses: actions/setup-python@v5
|
|
with:
|
|
python-version: ${{ matrix.python-version }}
|
|
- name: Install dependencies
|
|
run: |
|
|
pip install -r requirements.txt
|
|
pip install pytest pytest-cov
|
|
- name: Run tests
|
|
run: pytest --cov=. --cov-report=xml
|
|
- name: Upload coverage
|
|
uses: codecov/codecov-action@v4
|
|
if: matrix.python-version == '3.12'
|
|
```
|
|
|
|
### 1.2 Lint
|
|
```yaml
|
|
lint:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- name: Set up Python
|
|
uses: actions/setup-python@v5
|
|
with:
|
|
python-version: '3.12'
|
|
- name: Install linters
|
|
run: pip install ruff black isort mypy
|
|
- name: Run ruff
|
|
run: ruff check .
|
|
- name: Check formatting
|
|
run: black --check .
|
|
- name: Check import order
|
|
run: isort --check-only .
|
|
- name: Type check
|
|
run: mypy . --ignore-missing-imports
|
|
```
|
|
|
|
### 1.3 Build
|
|
```yaml
|
|
build:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- name: Build Docker image
|
|
run: docker build -t test-build .
|
|
- name: Test container starts
|
|
run: |
|
|
docker run -d --name test-container test-build
|
|
sleep 5
|
|
docker logs test-container
|
|
docker stop test-container
|
|
```
|
|
|
|
**Outputs:**
|
|
✅ All checks pass → PR can be merged
|
|
❌ Any check fails → PR blocked until fixed
|
|
|
|
---
|
|
|
|
## Stage 2: Pull Request Flow
|
|
|
|
**Process:**
|
|
|
|
1. **Developer creates PR** with standardized template:
|
|
```markdown
|
|
## What
|
|
Brief description of changes
|
|
|
|
## Why
|
|
Business/technical justification
|
|
|
|
## How
|
|
Implementation approach
|
|
|
|
## Testing
|
|
- [ ] Unit tests added/updated
|
|
- [ ] Integration tests pass
|
|
- [ ] Manual testing completed
|
|
|
|
## Screenshots/Logs
|
|
(if applicable)
|
|
|
|
## Related
|
|
- Asana Task: [link]
|
|
- Salesforce Project: [link]
|
|
```
|
|
|
|
2. **Automated checks run:**
|
|
- CI pipeline (test + lint + build)
|
|
- CODEOWNERS review assignment
|
|
- Label auto-applied based on files changed
|
|
|
|
3. **Human review:**
|
|
- At least 1 approval required
|
|
- No unresolved conversations
|
|
- All checks green
|
|
|
|
4. **Merge:**
|
|
- Squash and merge (clean history)
|
|
- Auto-delete branch after merge
|
|
|
|
---
|
|
|
|
## Stage 3: Continuous Deployment (CD)
|
|
|
|
**Trigger:**
|
|
- Push to `main` branch
|
|
- GitHub Release created (for versioned deploys)
|
|
|
|
**GitHub Action:** `.github/workflows/deploy.yml`
|
|
|
|
### 3.1 Deploy Backend (Railway)
|
|
|
|
```yaml
|
|
deploy-backend:
|
|
runs-on: ubuntu-latest
|
|
environment: production
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- name: Install Railway CLI
|
|
run: |
|
|
curl -fsSL https://railway.app/install.sh | sh
|
|
|
|
- name: Deploy to Railway
|
|
env:
|
|
RAILWAY_TOKEN: ${{ secrets.RAILWAY_TOKEN }}
|
|
run: |
|
|
railway up --service backend --environment production
|
|
|
|
- name: Wait for deployment
|
|
run: |
|
|
railway status --service backend --environment production --wait
|
|
|
|
- name: Run post-deploy health check
|
|
run: |
|
|
BACKEND_URL=$(railway variables get BACKEND_URL --service backend --environment production)
|
|
curl -f "$BACKEND_URL/health" || exit 1
|
|
|
|
- name: Save deploy metadata
|
|
run: |
|
|
echo "DEPLOY_TIME=$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> $GITHUB_ENV
|
|
echo "DEPLOY_SHA=${{ github.sha }}" >> $GITHUB_ENV
|
|
echo "DEPLOY_BRANCH=${{ github.ref_name }}" >> $GITHUB_ENV
|
|
```
|
|
|
|
### 3.2 Deploy Frontend (Cloudflare Pages)
|
|
|
|
```yaml
|
|
deploy-frontend:
|
|
runs-on: ubuntu-latest
|
|
environment: production
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@v4
|
|
with:
|
|
node-version: '20'
|
|
|
|
- name: Install dependencies
|
|
run: npm ci
|
|
|
|
- name: Build frontend
|
|
env:
|
|
VITE_API_URL: ${{ secrets.PROD_API_URL }}
|
|
VITE_ENV: production
|
|
run: npm run build
|
|
|
|
- name: Deploy to Cloudflare Pages
|
|
uses: cloudflare/pages-action@v1
|
|
with:
|
|
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
|
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
|
projectName: blackroad-frontend
|
|
directory: dist
|
|
gitHubToken: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
- name: Verify deployment
|
|
run: |
|
|
sleep 30 # Wait for propagation
|
|
curl -f "https://blackroad.app" || exit 1
|
|
```
|
|
|
|
### 3.3 Update Infrastructure (if ops repo)
|
|
|
|
```yaml
|
|
update-infra:
|
|
runs-on: ubuntu-latest
|
|
if: contains(github.repository, '-ops')
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- name: Apply Terraform changes
|
|
env:
|
|
TF_VAR_railway_token: ${{ secrets.RAILWAY_TOKEN }}
|
|
TF_VAR_cloudflare_token: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
|
run: |
|
|
cd terraform
|
|
terraform init
|
|
terraform plan -out=tfplan
|
|
terraform apply -auto-approve tfplan
|
|
|
|
- name: Update Cloudflare DNS
|
|
if: contains(github.event.head_commit.message, '[dns]')
|
|
run: |
|
|
# Apply DNS changes from dns-config.json
|
|
python scripts/update_cloudflare_dns.py
|
|
```
|
|
|
|
---
|
|
|
|
## Stage 4: Stakeholder Notification
|
|
|
|
**Trigger:**
|
|
Deploy jobs complete successfully
|
|
|
|
**GitHub Action:** `.github/workflows/notify-stakeholders.yml`
|
|
|
|
### 4.1 Update Salesforce
|
|
|
|
```yaml
|
|
update-salesforce:
|
|
needs: [deploy-backend, deploy-frontend]
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Get Project Key from repo
|
|
run: |
|
|
# Extract from repo name: blackroad-ACME-X7K9-backend → ACME-X7K9
|
|
REPO_NAME="${{ github.repository }}"
|
|
PROJECT_KEY=$(echo "$REPO_NAME" | sed -n 's/.*blackroad-\([A-Z0-9-]*\)-.*/\1/p')
|
|
echo "PROJECT_KEY=$PROJECT_KEY" >> $GITHUB_ENV
|
|
|
|
- name: Update Salesforce Project record
|
|
env:
|
|
SF_INSTANCE_URL: ${{ secrets.SALESFORCE_INSTANCE_URL }}
|
|
SF_ACCESS_TOKEN: ${{ secrets.SALESFORCE_ACCESS_TOKEN }}
|
|
run: |
|
|
curl -X PATCH \
|
|
"$SF_INSTANCE_URL/services/data/v58.0/sobjects/Project__c/Project_Key__c/$PROJECT_KEY" \
|
|
-H "Authorization: Bearer $SF_ACCESS_TOKEN" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"Last_Deploy_At__c": "'"$(date -u +%Y-%m-%dT%H:%M:%SZ)"'",
|
|
"Last_Deploy_SHA__c": "${{ github.sha }}",
|
|
"Last_Deploy_Branch__c": "${{ github.ref_name }}",
|
|
"Last_Deploy_Actor__c": "${{ github.actor }}",
|
|
"Deploy_Status__c": "Success",
|
|
"Environment__c": "Production",
|
|
"Release_Notes_URL__c": "https://github.com/${{ github.repository }}/commit/${{ github.sha }}"
|
|
}'
|
|
|
|
- name: Create Salesforce deployment record
|
|
run: |
|
|
curl -X POST \
|
|
"$SF_INSTANCE_URL/services/data/v58.0/sobjects/Deployment__c" \
|
|
-H "Authorization: Bearer $SF_ACCESS_TOKEN" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"Project__c": "'"$PROJECT_KEY"'",
|
|
"Deployed_At__c": "'"$(date -u +%Y-%m-%dT%H:%M:%SZ)"'",
|
|
"Git_SHA__c": "${{ github.sha }}",
|
|
"Git_Branch__c": "${{ github.ref_name }}",
|
|
"Deployed_By__c": "${{ github.actor }}",
|
|
"Status__c": "Success",
|
|
"Repository__c": "${{ github.repository }}",
|
|
"Commit_URL__c": "https://github.com/${{ github.repository }}/commit/${{ github.sha }}"
|
|
}'
|
|
```
|
|
|
|
### 4.2 Update Asana
|
|
|
|
```yaml
|
|
update-asana:
|
|
needs: [deploy-backend, deploy-frontend]
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Get Asana project and task IDs
|
|
env:
|
|
ASANA_PAT: ${{ secrets.ASANA_PAT }}
|
|
PROJECT_KEY: ${{ env.PROJECT_KEY }}
|
|
run: |
|
|
# Find project by name containing PROJECT_KEY
|
|
PROJECT_GID=$(curl -s "https://app.asana.com/api/1.0/projects?workspace=${{ secrets.ASANA_WORKSPACE_GID }}&opt_fields=name,gid" \
|
|
-H "Authorization: Bearer $ASANA_PAT" | \
|
|
jq -r '.data[] | select(.name | contains("'$PROJECT_KEY'")) | .gid')
|
|
|
|
echo "ASANA_PROJECT_GID=$PROJECT_GID" >> $GITHUB_ENV
|
|
|
|
- name: Find and complete deploy task
|
|
run: |
|
|
# Find "Deploy to production" task
|
|
TASK_GID=$(curl -s "https://app.asana.com/api/1.0/projects/$ASANA_PROJECT_GID/tasks?opt_fields=name,gid,completed" \
|
|
-H "Authorization: Bearer $ASANA_PAT" | \
|
|
jq -r '.data[] | select(.name | contains("Deploy") and (.completed == false)) | .gid' | head -n 1)
|
|
|
|
if [ -n "$TASK_GID" ]; then
|
|
# Mark as complete
|
|
curl -X PUT "https://app.asana.com/api/1.0/tasks/$TASK_GID" \
|
|
-H "Authorization: Bearer $ASANA_PAT" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"data": {"completed": true}}'
|
|
|
|
# Add comment with deploy details
|
|
curl -X POST "https://app.asana.com/api/1.0/tasks/$TASK_GID/stories" \
|
|
-H "Authorization: Bearer $ASANA_PAT" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"data": {
|
|
"text": "✅ Deployed to production\n\n**Commit:** ${{ github.sha }}\n**By:** ${{ github.actor }}\n**Time:** '"$(date -u)"'\n**Link:** https://github.com/${{ github.repository }}/commit/${{ github.sha }}"
|
|
}
|
|
}'
|
|
fi
|
|
```
|
|
|
|
### 4.3 Notify Slack
|
|
|
|
```yaml
|
|
notify-slack:
|
|
needs: [update-salesforce, update-asana]
|
|
runs-on: ubuntu-latest
|
|
if: always()
|
|
steps:
|
|
- name: Post to #deploys channel
|
|
env:
|
|
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_DEPLOYS }}
|
|
run: |
|
|
STATUS_EMOJI="${{ job.status == 'success' && '✅' || '❌' }}"
|
|
curl -X POST "$SLACK_WEBHOOK_URL" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"blocks": [
|
|
{
|
|
"type": "section",
|
|
"text": {
|
|
"type": "mrkdwn",
|
|
"text": "'"$STATUS_EMOJI"' *Deploy to Production*\n\n*Project:* '"$PROJECT_KEY"'\n*Repo:* `${{ github.repository }}`\n*Commit:* <https://github.com/${{ github.repository }}/commit/${{ github.sha }}|'"${GITHUB_SHA:0:7}"'>\n*By:* ${{ github.actor }}\n*Status:* ${{ job.status }}"
|
|
}
|
|
}
|
|
]
|
|
}'
|
|
```
|
|
|
|
---
|
|
|
|
## Stage 5: Rollback (if needed)
|
|
|
|
**Trigger:** Manual action or automated health check failure
|
|
|
|
**GitHub Action:** `.github/workflows/rollback.yml`
|
|
|
|
```yaml
|
|
name: Rollback Deployment
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
inputs:
|
|
target_sha:
|
|
description: 'Git SHA to rollback to'
|
|
required: true
|
|
reason:
|
|
description: 'Reason for rollback'
|
|
required: true
|
|
|
|
jobs:
|
|
rollback:
|
|
runs-on: ubuntu-latest
|
|
environment: production
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
ref: ${{ github.event.inputs.target_sha }}
|
|
|
|
- name: Rollback backend
|
|
run: |
|
|
railway up --service backend --environment production
|
|
|
|
- name: Rollback frontend
|
|
run: |
|
|
npm ci
|
|
npm run build
|
|
# Deploy to Cloudflare...
|
|
|
|
- name: Update Salesforce
|
|
run: |
|
|
curl -X POST "$SF_INSTANCE_URL/services/data/v58.0/sobjects/Deployment__c" \
|
|
-H "Authorization: Bearer $SF_ACCESS_TOKEN" \
|
|
-d '{
|
|
"Project__c": "'"$PROJECT_KEY"'",
|
|
"Status__c": "Rollback",
|
|
"Git_SHA__c": "${{ github.event.inputs.target_sha }}",
|
|
"Rollback_Reason__c": "${{ github.event.inputs.reason }}"
|
|
}'
|
|
|
|
- name: Notify team
|
|
run: |
|
|
curl -X POST "$SLACK_WEBHOOK_URL" \
|
|
-d '{
|
|
"text": "⚠️ *ROLLBACK PERFORMED*\n\nProject: '"$PROJECT_KEY"'\nRolled back to: '"${{ github.event.inputs.target_sha }}"'\nReason: '"${{ github.event.inputs.reason }}"'\nBy: '"${{ github.actor }}"'"
|
|
}'
|
|
```
|
|
|
|
---
|
|
|
|
## Human Touch Points
|
|
|
|
**What Developers Do:**
|
|
|
|
1. Write code in feature branch
|
|
2. Create PR when ready
|
|
3. Address review feedback
|
|
4. Merge PR (or approve auto-merge)
|
|
5. **That's it.** Everything else is automatic.
|
|
|
|
**What Ops/Brenda Sees:**
|
|
|
|
1. Slack notification: "✅ Deploy to production: ACME-X7K9"
|
|
2. Asana task auto-completes
|
|
3. Salesforce shows updated "Last Deploy" timestamp
|
|
4. No manual status updates needed
|
|
|
|
**When to Intervene:**
|
|
|
|
- Deploy fails (red X in GitHub Actions)
|
|
- Health check fails post-deploy
|
|
- Customer reports issue immediately after deploy
|
|
- → Use rollback workflow
|
|
|
|
---
|
|
|
|
## Metrics Dashboard
|
|
|
|
Track these in Salesforce or a monitoring tool:
|
|
|
|
| Metric | Target | Current |
|
|
|--------|--------|---------|
|
|
| **Deploy Frequency** | > 5/week per project | - |
|
|
| **Lead Time** (commit → production) | < 30 minutes | - |
|
|
| **Change Failure Rate** | < 15% | - |
|
|
| **MTTR** (Mean Time to Recovery) | < 1 hour | - |
|
|
| **Deploy Success Rate** | > 95% | - |
|
|
|
|
---
|
|
|
|
## Validation Checklist
|
|
|
|
After each deploy, verify:
|
|
|
|
- [ ] CI pipeline passed all checks
|
|
- [ ] Backend health check returns 200
|
|
- [ ] Frontend loads without errors
|
|
- [ ] Database migrations applied (if any)
|
|
- [ ] Salesforce Project record updated
|
|
- [ ] Asana task marked complete
|
|
- [ ] Slack notification sent
|
|
- [ ] No alerts in monitoring
|
|
|
|
---
|
|
|
|
## Troubleshooting
|
|
|
|
| Symptom | Likely Cause | Fix |
|
|
|---------|-------------|-----|
|
|
| CI fails on test | Code issue | Fix tests, push again |
|
|
| Deploy fails on Railway | Token expired | Refresh `RAILWAY_TOKEN` secret |
|
|
| Salesforce not updating | Wrong project key | Verify repo name matches `blackroad-{PROJECT_KEY}-*` |
|
|
| Asana task not completing | Task name doesn't match | Ensure task name contains "Deploy" |
|
|
| Health check fails | Backend not fully started | Increase sleep time in workflow |
|
|
|
|
---
|
|
|
|
## Related Docs
|
|
|
|
- [GitHub Actions: CI Workflow](../templates/github-actions/ci.yml)
|
|
- [GitHub Actions: Deploy Workflow](../templates/github-actions/deploy.yml)
|
|
- [GitHub Actions: Notify Stakeholders](../templates/github-actions/notify-stakeholders.yml)
|
|
- [Integration: GitHub → Salesforce](../integrations/github-to-salesforce.md)
|
|
- [New Client Kickoff Workflow](./new-client-kickoff.md)
|
|
|
|
---
|
|
|
|
## Philosophy
|
|
|
|
**"Deploy should be boring."**
|
|
|
|
The goal is to make deployments so reliable, automated, and well-monitored that they become **non-events**.
|
|
|
|
Every commit to `main` is a potential release. Every release updates all stakeholders automatically. No human should ever ask "Did this deploy?" or "What version is in production?"
|
|
|
|
The system should always know.
|