Add complete automation SOP system for BlackRoad ERP

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.
This commit is contained in:
Claude
2025-11-17 08:17:51 +00:00
parent 9b137af555
commit 7cde897040
18 changed files with 6077 additions and 0 deletions

580
sop/README.md Normal file
View File

@@ -0,0 +1,580 @@
# BlackRoad Automation SOP
**Version:** 1.0
**Last Updated:** 2025-11-17
**Status:** Active
---
## What This Is
This directory contains the **complete Standard Operating Procedures (SOPs)** for BlackRoad's automated ERP system, which integrates:
- **GitHub** (source control + CI/CD automation)
- **Salesforce** (customer data + business logic)
- **Asana** (project management + task tracking)
**Goal:** Automate the entire journey from "deal closed" to "code deployed" with minimal human intervention.
---
## Quick Start
### For Operators (Brenda)
**Start Here:** [Brenda's New Client Checklist](./playbooks/brenda-new-client-checklist.md)
This is your step-by-step guide for onboarding new clients. No technical knowledge required.
---
### For Engineers
**Start Here:** [New Client Kickoff Workflow](./workflows/new-client-kickoff.md)
Understand how the automation works end-to-end.
**Then:** [Release Pipeline Workflow](./workflows/release-pipeline.md)
Learn how deployments trigger automatic updates across systems.
---
### For Salesforce Admins
**Start Here:**
1. [Salesforce Flow: Opportunity Automation](./salesforce/flows/opp-automation-onstagechange.md)
2. [Salesforce Orchestration: New Client Kickoff](./salesforce/orchestrations/new-client-kickoff-orchestration.md)
These contain detailed specs for building the flows in Salesforce.
---
## Directory Structure
```
sop/
├── README.md ← You are here
├── workflows/ ← End-to-end process documentation
│ ├── new-client-kickoff.md ← Flagship workflow: 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 specs
│ ├── 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 workflow templates
│ │ ├── ci.yml ← Test, lint, build
│ │ ├── deploy.yml ← Deploy to Railway/Cloudflare
│ │ └── safety.yml ← Security scanning
│ └── repo-template/ ← Standard repo configuration
│ └── .github/
│ ├── pull_request_template.md
│ ├── labels.json
│ ├── branch-protection.md
│ └── ISSUE_TEMPLATE/
│ ├── bug_report.md
│ ├── feature_request.md
│ └── deployment_checklist.md
└── prompts/ ← Claude/Cece prompt seeds
└── (future: automation setup prompts)
```
---
## The Golden Path (How It All Works)
### 1. Salesforce: Deal Closes
**Human Action:** Sales marks Opportunity as "Closed Won"
**Automation:**
- Salesforce Flow creates Project record
- Orchestration kicks off (4 stages)
- Project Key generated (e.g., `ACME-1042`)
**Doc:** [Opp Automation Flow](./salesforce/flows/opp-automation-onstagechange.md)
---
### 2. GitHub: Repos Created
**Trigger:** Orchestration Stage 2 (Technical Setup)
**Automation:**
- Salesforce calls GitHub API
- 3 repos created:
- `blackroad-{PROJECT_KEY}-backend`
- `blackroad-{PROJECT_KEY}-frontend`
- `blackroad-{PROJECT_KEY}-ops`
- CI/CD workflows added
- Branch protection enabled
- Labels applied
- Secrets configured
**Doc:** [Salesforce → GitHub Integration](./integrations/salesforce-to-github.md)
---
### 3. Asana: Project Board Created
**Trigger:** Same Orchestration Stage 2
**Automation:**
- Salesforce calls Asana API
- Project created with sections:
- Discovery
- Architecture
- Build
- Testing
- Go-Live
- ~8-10 standard tasks created
- Tasks assigned with due dates
- Links back to Salesforce + GitHub
**Doc:** [Salesforce → Asana Integration](./integrations/salesforce-to-asana.md)
---
### 4. Engineers Work
**Human Action:** Engineers write code, create PRs, merge to `main`
**Automation:**
- GitHub Actions run CI pipeline (test + lint + build)
- PR must pass checks + get approval
- Merge triggers deploy pipeline
- Code deployed to Railway + Cloudflare
**Doc:** [Release Pipeline Workflow](./workflows/release-pipeline.md)
---
### 5. Feedback Loop
**Trigger:** Deploy succeeds
**Automation:**
- GitHub Actions call Salesforce API
- Update Project record:
- `Last_Deploy_At__c`
- `Last_Deploy_SHA__c`
- `Deploy_Status__c`
- Create Deployment record for audit
- Call Asana API to mark "Deploy to production" task complete
- Post deploy notification to Slack
**Doc:** [GitHub → Salesforce Integration](./integrations/github-to-salesforce.md)
---
### 6. Go-Live
**Human Action:** Customer Success does final walkthrough with client
**Automation:**
- Project status updated to "Active"
- Go-live email sent automatically
- Renewal opportunity created
- Success metrics dashboard initialized
**Doc:** [New Client Kickoff Orchestration](./salesforce/orchestrations/new-client-kickoff-orchestration.md)
---
## Implementation Phases
### Phase 1: Foundation (Week 1)
**Goal:** Get basic automation working
**Tasks:**
- [ ] Create Salesforce custom objects (Project__c, Deployment__c)
- [ ] Set up Salesforce Named Credentials (GitHub, Asana)
- [ ] Create test GitHub repos manually
- [ ] Apply GitHub Actions workflows from templates
- [ ] Test Salesforce → GitHub API call manually
**Docs:**
- [Salesforce Flow Spec](./salesforce/flows/opp-automation-onstagechange.md)
- [Salesforce → GitHub Integration](./integrations/salesforce-to-github.md)
---
### Phase 2: Core Workflow (Week 2-3)
**Goal:** Automate new client kickoff
**Tasks:**
- [ ] Build Salesforce Flow: Opp_Automation_OnStageChange
- [ ] Build Salesforce Orchestration: New_Client_Kickoff
- [ ] Implement GitHub repo creation (via Flow)
- [ ] Implement Asana project creation (via Flow)
- [ ] Test end-to-end with 1 test client
**Docs:**
- [New Client Kickoff Workflow](./workflows/new-client-kickoff.md)
- [Salesforce Orchestration Spec](./salesforce/orchestrations/new-client-kickoff-orchestration.md)
---
### Phase 3: Feedback Loop (Week 4)
**Goal:** Close the loop with deploy notifications
**Tasks:**
- [ ] Add GitHub → Salesforce workflow to repos
- [ ] Test deploy updates Salesforce Project record
- [ ] Add GitHub → Asana integration (mark tasks complete)
- [ ] Set up Slack notifications
- [ ] Deploy to 3 pilot projects
**Docs:**
- [Release Pipeline Workflow](./workflows/release-pipeline.md)
- [GitHub → Salesforce Integration](./integrations/github-to-salesforce.md)
---
### Phase 4: Scale (Week 5+)
**Goal:** Roll out to all new clients
**Tasks:**
- [ ] Train operations team on new process
- [ ] Document troubleshooting steps
- [ ] Create monitoring dashboard
- [ ] Roll out to all new deals
- [ ] Migrate existing clients gradually
**Docs:**
- [Brenda's Checklist](./playbooks/brenda-new-client-checklist.md)
---
## Key Principles
### 1. Event-Driven Everything
**Old Way:** "Brenda, can you create the repos and set up Asana?"
**New Way:** Mark opportunity as Closed Won → everything happens automatically
---
### 2. GitHub-First Configuration
All workflows, templates, and configs live in **version control** (this repo).
Changes go through PR → review → merge → deploy.
---
### 3. Two Views
**Operator View:** Simple checklists, no jargon, clear escalation paths
**Engineer View:** Detailed specs, API payloads, error handling
---
### 4. No Manual Status Syncing
Status lives in **one place** (Salesforce Project record).
Everything else subscribes via API.
---
## Troubleshooting
### "Repos didn't get created after 15 minutes"
1. Check Salesforce debug logs for HTTP callout errors
2. Verify GitHub API credentials in Named Credential
3. Check GitHub App permissions
4. See: [Salesforce → GitHub Integration, Error Handling](./integrations/salesforce-to-github.md#error-handling)
---
### "Asana project is missing tasks"
1. Check Salesforce debug logs
2. Verify Asana PAT is valid
3. Check custom metadata: Asana_Task_Template__mdt
4. See: [Salesforce → Asana Integration, Error Handling](./integrations/salesforce-to-asana.md#error-handling)
---
### "Deploy didn't update Salesforce"
1. Check GitHub Actions workflow logs
2. Verify PROJECT_KEY was extracted correctly from repo name
3. Check Salesforce API credentials in GitHub secrets
4. See: [GitHub → Salesforce Integration, Error Handling](./integrations/github-to-salesforce.md#error-handling)
---
### "How do I report an automation bug?"
**In GitHub:**
1. Go to this repo
2. Create new issue
3. Use label: `automation-bug`
4. Include:
- Salesforce Project URL
- Expected vs. actual behavior
- Screenshots/logs
**In Slack:**
Post in #ops with:
- Project Key
- What broke
- Link to Salesforce Project
---
## Metrics & Monitoring
### Track These KPIs:
| Metric | Target | How to Measure |
|--------|--------|----------------|
| Time to First Commit | < 3 days | GitHub first commit - SF Start Date |
| Time to Go-Live | < 20 days | Go Live Date - Start Date |
| Automation Success Rate | > 95% | Projects with repos / Total projects |
| Manual Intervention Rate | < 10% | Projects needing fixes / Total |
| Deploy Frequency | > 5/week | Deploys per project per week |
| Deploy Success Rate | > 95% | Successful deploys / Total |
**Salesforce Reports:**
- "Projects by Status"
- "Deployments by Week"
- "Automation Errors (Last 30 Days)"
**GitHub Insights:**
- Actions usage
- Deploy frequency
- Build success rate
---
## Team Roles
### Operations (Brenda)
**Responsibilities:**
- Mark deals as Closed Won
- Verify automation ran successfully
- Communicate with clients
- Monitor Asana project progress
- Escalate issues to engineering
**Primary Doc:** [Brenda's Checklist](./playbooks/brenda-new-client-checklist.md)
---
### Salesforce Admin
**Responsibilities:**
- Build and maintain Flows + Orchestrations
- Manage Named Credentials
- Monitor API logs
- Troubleshoot Salesforce-side errors
**Primary Docs:**
- [Salesforce Flow Spec](./salesforce/flows/opp-automation-onstagechange.md)
- [Salesforce Orchestration Spec](./salesforce/orchestrations/new-client-kickoff-orchestration.md)
---
### DevOps / Engineering
**Responsibilities:**
- Maintain GitHub Actions workflows
- Configure repos via automation
- Monitor deploy pipelines
- Troubleshoot GitHub/Railway/Cloudflare issues
**Primary Docs:**
- [Release Pipeline Workflow](./workflows/release-pipeline.md)
- [GitHub Actions Templates](./templates/github-actions/)
---
### Integration Engineer
**Responsibilities:**
- Maintain API integrations
- Monitor API logs and rate limits
- Update integration specs
- Handle authentication issues
**Primary Docs:**
- [Salesforce → GitHub Integration](./integrations/salesforce-to-github.md)
- [GitHub → Salesforce Integration](./integrations/github-to-salesforce.md)
- [Salesforce → Asana Integration](./integrations/salesforce-to-asana.md)
---
## Security & Compliance
### Credentials Management
**Salesforce:**
- Use Named Credentials (not hardcoded tokens)
- Rotate OAuth tokens quarterly
- Use encrypted custom settings for sensitive data
**GitHub:**
- Use GitHub App (not PAT) for production
- Rotate secrets every 90 days
- Use organization-level secrets where possible
**Asana:**
- Use dedicated integration PAT
- Don't share PAT across integrations
- Rotate every 90 days
---
### Audit Trail
**Track:**
- All API calls (Salesforce Custom Object: API_Log__c)
- All deployments (Salesforce: Deployment__c)
- All automation errors (Cases with Type = "Automation Bug")
**Review:**
- Weekly: Error logs
- Monthly: Success rates, anomalies
- Quarterly: Security audit, credential rotation
---
## FAQs
### Q: What if I need to create a repo manually?
**A:** Follow the [Repo Template](./templates/repo-template/) to apply:
- Labels
- Branch protection
- Workflows
- PR template
- Issue templates
Then manually update Salesforce Project record with repo URLs.
---
### Q: Can I customize the Asana tasks for different package types?
**A:** Yes! Edit the Custom Metadata Type: `Asana_Task_Template__mdt`
Add records with conditions based on Package_Type__c.
---
### Q: How do I add a new GitHub Actions workflow to all repos?
**A:**
1. Add workflow to [templates/github-actions/](./templates/github-actions/)
2. Update Salesforce Flow to include new workflow in repo creation
3. For existing repos, use a script or PR to all repos
---
### Q: What if a client wants a custom domain (not .blackroad.app)?
**A:** Update the `Primary_Domain__c` field in Salesforce, then:
1. Configure Cloudflare custom domain
2. Update environment variables in Railway
3. Redeploy frontend
---
## Contributing
This SOP is **living documentation**. If you:
- Find an error
- Want to improve a process
- Have a better way to do something
**Submit a PR!**
1. Edit the relevant `.md` file
2. Create a PR with clear description
3. Tag @ops or @devops for review
4. Merge once approved
---
## Support
### Internal Support
**Slack Channels:**
- `#ops` - General operations questions
- `#dev` - Engineering / technical questions
- `#automation` - Automation bugs and improvements
**Email:**
- ops@blackroad.com - Operations team
- devops@blackroad.com - DevOps team
---
### External Resources
**Salesforce:**
- [Flow Builder Docs](https://help.salesforce.com/s/articleView?id=sf.flow.htm)
- [REST API Docs](https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/)
**GitHub:**
- [Actions Docs](https://docs.github.com/en/actions)
- [REST API Docs](https://docs.github.com/en/rest)
**Asana:**
- [API Docs](https://developers.asana.com/docs)
---
## Changelog
| Date | Version | Change | Author |
|------|---------|--------|--------|
| 2025-11-17 | 1.0 | Initial SOP created - complete automation system | Cece (Claude) |
---
## Next Steps
**If you're here to implement this:**
1. **Week 1:** Read [New Client Kickoff Workflow](./workflows/new-client-kickoff.md)
2. **Week 2:** Set up Salesforce objects + credentials
3. **Week 3:** Build the Salesforce Flow
4. **Week 4:** Test with 1 test client end-to-end
5. **Week 5:** Roll out to production
**If you're here to use this:**
- **Operators:** [Brenda's Checklist](./playbooks/brenda-new-client-checklist.md)
- **Engineers:** [Release Pipeline](./workflows/release-pipeline.md)
- **Salesforce Admins:** [Flow Spec](./salesforce/flows/opp-automation-onstagechange.md)
---
**Welcome to Automate The Company Day. Let's make it happen.** 🚀

View File

@@ -0,0 +1,567 @@
# Integration: GitHub → Salesforce
**Purpose:** Enable GitHub Actions to update Salesforce records after deployments
**Direction:** GitHub calls Salesforce REST API
**Authentication:** OAuth 2.0 (Connected App) or Named Credential
**Status:** Active
**Last Updated:** 2025-11-17
---
## Overview
This integration allows GitHub Actions workflows to:
- Update Project records with deployment metadata
- Create Deployment records for audit trail
- Trigger Salesforce flows/automations
- Close tasks or update statuses
**Key Use Case:** Auto-update Salesforce when code is deployed to production
---
## Authentication Setup
### Option A: Salesforce Connected App (Recommended)
**Benefits:**
- OAuth 2.0 standard
- Refresh tokens
- IP restrictions
- Better audit trail
**Setup Steps:**
#### 1. Create Connected App in Salesforce
1. **Setup → App Manager → New Connected App**
2. **Basic Information:**
- Connected App Name: `GitHub Actions Integration`
- API Name: `GitHub_Actions_Integration`
- Contact Email: devops@blackroad.com
3. **API (Enable OAuth Settings):**
- ✅ Enable OAuth Settings
- Callback URL: `https://login.salesforce.com/services/oauth2/callback`
- Selected OAuth Scopes:
- `api` - Perform requests at any time
- `refresh_token, offline_access` - Perform requests at any time
4. **Save** and wait 2-10 minutes for Consumer Key/Secret to be generated
5. **Get Credentials:**
- Consumer Key (Client ID): `3MVG9...`
- Consumer Secret (Client Secret): `ABC123...`
#### 2. Create GitHub Secrets
In each project repository (or organization-level):
1. Go to: Settings → Secrets and variables → Actions
2. Add these secrets:
| Secret Name | Value | Description |
|-------------|-------|-------------|
| `SALESFORCE_INSTANCE_URL` | `https://your-domain.my.salesforce.com` | Salesforce instance URL |
| `SALESFORCE_CLIENT_ID` | `3MVG9...` | Connected App Consumer Key |
| `SALESFORCE_CLIENT_SECRET` | `ABC123...` | Connected App Consumer Secret |
| `SALESFORCE_USERNAME` | `integration-user@blackroad.com` | Service account username |
| `SALESFORCE_PASSWORD` | `password123` | Service account password |
| `SALESFORCE_SECURITY_TOKEN` | `XYZ789...` | Security token for user |
**Security Best Practice:** Use a dedicated integration service account, not a personal user.
#### 3. Get Access Token in GitHub Actions
**Workflow Step:**
```yaml
- name: Get Salesforce Access Token
id: sf-auth
run: |
RESPONSE=$(curl -X POST "https://login.salesforce.com/services/oauth2/token" \
-d "grant_type=password" \
-d "client_id=${{ secrets.SALESFORCE_CLIENT_ID }}" \
-d "client_secret=${{ secrets.SALESFORCE_CLIENT_SECRET }}" \
-d "username=${{ secrets.SALESFORCE_USERNAME }}" \
-d "password=${{ secrets.SALESFORCE_PASSWORD }}${{ secrets.SALESFORCE_SECURITY_TOKEN }}")
ACCESS_TOKEN=$(echo $RESPONSE | jq -r '.access_token')
INSTANCE_URL=$(echo $RESPONSE | jq -r '.instance_url')
echo "ACCESS_TOKEN=$ACCESS_TOKEN" >> $GITHUB_OUTPUT
echo "INSTANCE_URL=$INSTANCE_URL" >> $GITHUB_OUTPUT
```
---
### Option B: Salesforce REST API Endpoint (Webhook Style)
**Setup Steps:**
1. **Create Apex REST Endpoint:**
```apex
@RestResource(urlMapping='/github/webhook')
global class GitHubWebhookHandler {
@HttpPost
global static String handleDeployment() {
RestRequest req = RestContext.request;
String body = req.requestBody.toString();
Map<String, Object> payload = (Map<String, Object>) JSON.deserializeUntyped(body);
String projectKey = (String) payload.get('project_key');
String deployedAt = (String) payload.get('deployed_at');
String gitSHA = (String) payload.get('git_sha');
String deployedBy = (String) payload.get('deployed_by');
// Find Project by external ID
Project__c project = [
SELECT Id, Name
FROM Project__c
WHERE Project_Key__c = :projectKey
LIMIT 1
];
// Update Project
project.Last_Deploy_At__c = Datetime.valueOf(deployedAt);
project.Last_Deploy_SHA__c = gitSHA;
project.Last_Deploy_Actor__c = deployedBy;
project.Deploy_Status__c = 'Success';
update project;
// Create Deployment record
Deployment__c deployment = new Deployment__c(
Project__c = project.Id,
Deployed_At__c = Datetime.valueOf(deployedAt),
Git_SHA__c = gitSHA,
Deployed_By__c = deployedBy,
Status__c = 'Success'
);
insert deployment;
return JSON.serialize(new Map<String, Object>{
'success' => true,
'project_id' => project.Id,
'deployment_id' => deployment.Id
});
}
}
```
2. **Configure Site Guest User Permissions:**
- Grant access to `Project__c` and `Deployment__c` objects
- Or use API key authentication in Apex
3. **Get Endpoint URL:**
- `https://your-domain.my.salesforce.com/services/apexrest/github/webhook`
4. **Add to GitHub Secrets:**
- `SALESFORCE_WEBHOOK_URL`: Full endpoint URL
---
## API Operations
### 1. Update Project Record (Upsert by External ID)
**Endpoint:**
```
PATCH {INSTANCE_URL}/services/data/v58.0/sobjects/Project__c/Project_Key__c/{PROJECT_KEY}
```
**Headers:**
```
Authorization: Bearer {ACCESS_TOKEN}
Content-Type: application/json
```
**Payload:**
```json
{
"Last_Deploy_At__c": "2025-11-17T14:30:00Z",
"Last_Deploy_SHA__c": "a1b2c3d4e5f6",
"Last_Deploy_Branch__c": "main",
"Last_Deploy_Actor__c": "github-user",
"Deploy_Status__c": "Success",
"Environment__c": "Production",
"Backend_URL__c": "https://backend.railway.app",
"Release_Notes_URL__c": "https://github.com/org/repo/commit/a1b2c3d4e5f6"
}
```
**Response (200 OK or 201 Created):**
```json
{
"id": "a0X5e000000XYZ1EAO",
"success": true,
"errors": []
}
```
**GitHub Actions Implementation:**
```yaml
- name: Update Salesforce Project record
env:
SF_INSTANCE_URL: ${{ steps.sf-auth.outputs.INSTANCE_URL }}
SF_ACCESS_TOKEN: ${{ steps.sf-auth.outputs.ACCESS_TOKEN }}
run: |
# Extract PROJECT_KEY from repo name
REPO_NAME="${{ github.repository }}"
PROJECT_KEY=$(echo "$REPO_NAME" | sed -n 's/.*blackroad-\([A-Z0-9-]*\)-.*/\1/p')
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 }}"
}'
```
---
### 2. Create Deployment Record
**Endpoint:**
```
POST {INSTANCE_URL}/services/data/v58.0/sobjects/Deployment__c
```
**Headers:**
```
Authorization: Bearer {ACCESS_TOKEN}
Content-Type: application/json
```
**Payload:**
```json
{
"Name": "ACME-1042 - a1b2c3d4",
"Project_Key__c": "ACME-1042",
"Deployed_At__c": "2025-11-17T14:30:00Z",
"Git_SHA__c": "a1b2c3d4e5f6",
"Git_Branch__c": "main",
"Deployed_By__c": "github-user",
"Status__c": "Success",
"Environment__c": "Production",
"Repository__c": "blackboxprogramming/blackroad-ACME-1042-backend",
"Commit_URL__c": "https://github.com/blackboxprogramming/blackroad-ACME-1042-backend/commit/a1b2c3d4e5f6",
"Duration_Seconds__c": 120
}
```
**Response (201 Created):**
```json
{
"id": "a0Y5e000000ABC1EAO",
"success": true,
"errors": []
}
```
**GitHub Actions Implementation:**
```yaml
- 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 '{
"Name": "'"$PROJECT_KEY"' - '"${GITHUB_SHA:0:8}"'",
"Project_Key__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",
"Environment__c": "Production",
"Repository__c": "${{ github.repository }}",
"Commit_URL__c": "https://github.com/${{ github.repository }}/commit/${{ github.sha }}"
}'
```
---
### 3. Query Salesforce (Optional)
**Use Case:** Get Project metadata before deploying
**Endpoint:**
```
GET {INSTANCE_URL}/services/data/v58.0/query?q=SELECT+Id,Name,Package_Type__c+FROM+Project__c+WHERE+Project_Key__c='ACME-1042'
```
**Headers:**
```
Authorization: Bearer {ACCESS_TOKEN}
```
**Response:**
```json
{
"totalSize": 1,
"done": true,
"records": [
{
"Id": "a0X5e000000XYZ1EAO",
"Name": "Acme Corp - ACME-1042",
"Package_Type__c": "OS"
}
]
}
```
---
## Required Salesforce Objects
### Deployment Custom Object
**API Name:** `Deployment__c`
**Label:** Deployment
**Record Name:** Deployment Name (Auto Number: `DEP-{0000}`)
#### Fields:
| Field API Name | Type | Length | Description |
|----------------|------|--------|-------------|
| `Project__c` | Lookup(Project__c) | N/A | Related project |
| `Project_Key__c` | Text (External ID) | 20 | For upsert operations |
| `Deployed_At__c` | DateTime | N/A | When deployment occurred |
| `Git_SHA__c` | Text | 40 | Git commit SHA |
| `Git_Branch__c` | Text | 100 | Git branch name |
| `Deployed_By__c` | Text | 100 | GitHub username |
| `Status__c` | Picklist | N/A | Success, Failed, Rollback, In Progress |
| `Environment__c` | Picklist | N/A | Staging, Production |
| `Repository__c` | Text | 255 | Full repo name (org/repo) |
| `Commit_URL__c` | URL | 255 | Link to commit |
| `Duration_Seconds__c` | Number(6,0) | N/A | Deploy duration |
| `Error_Message__c` | Long Text Area | 32768 | If deploy failed |
---
## Complete GitHub Actions Workflow
**File:** `.github/workflows/notify-salesforce.yml`
```yaml
name: Notify Salesforce After Deploy
on:
workflow_run:
workflows: ["Deploy to Production"]
types:
- completed
jobs:
notify-salesforce:
runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion == 'success' }}
steps:
- name: Extract Project Key from repo name
id: project-key
run: |
REPO_NAME="${{ github.repository }}"
PROJECT_KEY=$(echo "$REPO_NAME" | sed -n 's/.*blackroad-\([A-Z0-9-]*\)-.*/\1/p')
if [ -z "$PROJECT_KEY" ]; then
echo "Warning: Could not extract project key"
PROJECT_KEY="UNKNOWN"
fi
echo "PROJECT_KEY=$PROJECT_KEY" >> $GITHUB_OUTPUT
- name: Authenticate with Salesforce
id: sf-auth
env:
SF_CLIENT_ID: ${{ secrets.SALESFORCE_CLIENT_ID }}
SF_CLIENT_SECRET: ${{ secrets.SALESFORCE_CLIENT_SECRET }}
SF_USERNAME: ${{ secrets.SALESFORCE_USERNAME }}
SF_PASSWORD: ${{ secrets.SALESFORCE_PASSWORD }}
SF_SECURITY_TOKEN: ${{ secrets.SALESFORCE_SECURITY_TOKEN }}
run: |
RESPONSE=$(curl -X POST "https://login.salesforce.com/services/oauth2/token" \
-d "grant_type=password" \
-d "client_id=$SF_CLIENT_ID" \
-d "client_secret=$SF_CLIENT_SECRET" \
-d "username=$SF_USERNAME" \
-d "password=$SF_PASSWORD$SF_SECURITY_TOKEN")
ACCESS_TOKEN=$(echo $RESPONSE | jq -r '.access_token')
INSTANCE_URL=$(echo $RESPONSE | jq -r '.instance_url')
echo "ACCESS_TOKEN=$ACCESS_TOKEN" >> $GITHUB_OUTPUT
echo "INSTANCE_URL=$INSTANCE_URL" >> $GITHUB_OUTPUT
- name: Update Salesforce Project record
if: steps.project-key.outputs.PROJECT_KEY != 'UNKNOWN'
env:
SF_INSTANCE_URL: ${{ steps.sf-auth.outputs.INSTANCE_URL }}
SF_ACCESS_TOKEN: ${{ steps.sf-auth.outputs.ACCESS_TOKEN }}
PROJECT_KEY: ${{ steps.project-key.outputs.PROJECT_KEY }}
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 }}"
}' || echo "Warning: Failed to update Project record"
- name: Create Deployment record
if: steps.project-key.outputs.PROJECT_KEY != 'UNKNOWN'
env:
SF_INSTANCE_URL: ${{ steps.sf-auth.outputs.INSTANCE_URL }}
SF_ACCESS_TOKEN: ${{ steps.sf-auth.outputs.ACCESS_TOKEN }}
PROJECT_KEY: ${{ steps.project-key.outputs.PROJECT_KEY }}
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 '{
"Name": "'"$PROJECT_KEY"' - '"${GITHUB_SHA:0:8}"'",
"Project_Key__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",
"Environment__c": "Production",
"Repository__c": "${{ github.repository }}",
"Commit_URL__c": "https://github.com/${{ github.repository }}/commit/${{ github.sha }}"
}' || echo "Warning: Failed to create Deployment record"
```
---
## Error Handling
### Common Errors
| Status Code | Error | Cause | Solution |
|-------------|-------|-------|----------|
| 401 | Unauthorized | Token expired | Re-authenticate |
| 403 | Forbidden | Insufficient permissions | Check user permissions in Salesforce |
| 404 | Not Found | Project_Key__c doesn't exist | Verify project key extraction logic |
| 400 | Bad Request | Invalid field value | Check datetime format (must be ISO 8601) |
### Retry Logic
```yaml
- name: Update Salesforce (with retries)
uses: nick-invision/retry@v2
with:
timeout_minutes: 2
max_attempts: 3
command: |
curl -X PATCH ... (Salesforce API call)
```
---
## Testing
### Manual Test (curl)
```bash
# 1. Get access token
ACCESS_TOKEN=$(curl -X POST "https://login.salesforce.com/services/oauth2/token" \
-d "grant_type=password" \
-d "client_id=YOUR_CLIENT_ID" \
-d "client_secret=YOUR_CLIENT_SECRET" \
-d "username=YOUR_USERNAME" \
-d "password=YOUR_PASSWORD_AND_TOKEN" | jq -r '.access_token')
# 2. Update Project
curl -X PATCH \
"https://your-domain.my.salesforce.com/services/data/v58.0/sobjects/Project__c/Project_Key__c/TEST-1234" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"Last_Deploy_At__c": "2025-11-17T14:30:00Z",
"Deploy_Status__c": "Success"
}'
```
### GitHub Actions Test
1. Push commit to test repo
2. Trigger deploy workflow
3. Verify Salesforce Project updated
4. Check Deployment record created
5. Review GitHub Actions logs for errors
---
## Monitoring
**Track These Metrics:**
| Metric | Target | Alert Threshold |
|--------|--------|-----------------|
| Salesforce API Success Rate | > 98% | < 95% |
| Avg API Response Time | < 1s | > 3s |
| Failed Updates | < 2% | > 5% |
**Salesforce Report:** "Deployments by Status (Last 30 Days)"
---
## Security Best Practices
1. **Use Service Account:**
- Create `integration-user@blackroad.com`
- Assign minimal permissions
- Enable API-only user (no login UI)
2. **Rotate Credentials:**
- Rotate passwords every 90 days
- Use GitHub encrypted secrets
- Never commit credentials to git
3. **IP Restrictions:**
- Whitelist GitHub Actions IP ranges in Salesforce
- See: https://api.github.com/meta
4. **Audit Logging:**
- Enable Salesforce Event Monitoring
- Log all API calls
- Review monthly
---
## Related Documentation
- [Salesforce REST API Docs](https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/)
- [GitHub Actions: Deploy Workflow](../templates/github-actions/deploy.yml)
- [Workflow: Release Pipeline](../workflows/release-pipeline.md)
- [Integration: Salesforce → GitHub](./salesforce-to-github.md)
---
## Changelog
| Date | Version | Change | Author |
|------|---------|--------|--------|
| 2025-11-17 | 1.0 | Initial specification | Cece (Claude) |

View File

@@ -0,0 +1,644 @@
# Integration: Salesforce → Asana
**Purpose:** Enable Salesforce to create and manage Asana projects and tasks automatically
**Direction:** Salesforce calls Asana REST API
**Authentication:** Personal Access Token (PAT)
**Status:** Active
**Last Updated:** 2025-11-17
---
## Overview
This integration allows Salesforce Flows and Orchestrations to:
- Create Asana projects
- Add sections to projects
- Create tasks with assignments and due dates
- Update task status
- Add comments to tasks
**Key Use Case:** Auto-create Asana project board when Opportunity closes
---
## Authentication Setup
### Asana Personal Access Token (PAT)
**Setup Steps:**
1. **Generate PAT:**
- Go to: https://app.asana.com/0/my-apps
- Click "Personal access tokens"
- Click "+ New access token"
- Name: `Salesforce Integration`
- Copy token (starts with `1/...`)
2. **Store in Salesforce:**
- Setup → Named Credentials → New
- Name: `Asana_API`
- URL: `https://app.asana.com/api/1.0`
- Identity Type: Named Principal
- Authentication Protocol: Custom
- Header: `Authorization: Bearer {YOUR_PAT}`
**Alternative:** Use Custom Settings
```
Setup → Custom Settings → New (Protected)
Name: Asana_Settings__c
Fields:
- API_Token__c (Text, Encrypted)
- Workspace_GID__c (Text)
- Team_GID__c (Text)
```
---
## Required Asana Setup
### 1. Get Workspace GID
```bash
curl "https://app.asana.com/api/1.0/workspaces" \
-H "Authorization: Bearer YOUR_PAT"
```
**Response:**
```json
{
"data": [
{
"gid": "1234567890123456",
"name": "BlackRoad Workspace",
"resource_type": "workspace"
}
]
}
```
**Store:** `WORKSPACE_GID = "1234567890123456"`
---
### 2. Get Team GID
```bash
curl "https://app.asana.com/api/1.0/organizations/1234567890123456/teams" \
-H "Authorization: Bearer YOUR_PAT"
```
**Response:**
```json
{
"data": [
{
"gid": "9876543210987654",
"name": "Engineering",
"resource_type": "team"
}
]
}
```
**Store:** `TEAM_GID = "9876543210987654"`
---
### 3. Get User GIDs for Assignments
```bash
curl "https://app.asana.com/api/1.0/users?workspace=1234567890123456" \
-H "Authorization: Bearer YOUR_PAT"
```
**Response:**
```json
{
"data": [
{
"gid": "1111111111111111",
"name": "Alice Developer",
"email": "alice@blackroad.com"
},
{
"gid": "2222222222222222",
"name": "Bob DevOps",
"email": "bob@blackroad.com"
}
]
}
```
**Create Salesforce Mapping:**
| Role | Email | Asana GID |
|------|-------|-----------|
| DevOps Lead | bob@blackroad.com | 2222222222222222 |
| Backend Lead | alice@blackroad.com | 1111111111111111 |
| Customer Success | brenda@blackroad.com | 3333333333333333 |
Store in Custom Metadata Type: `Asana_User_Mapping__mdt`
---
## API Operations
### 1. Create Project
**Endpoint:**
```
POST https://app.asana.com/api/1.0/projects
```
**Headers:**
```
Authorization: Bearer {ASANA_PAT}
Content-Type: application/json
```
**Payload:**
```json
{
"data": {
"workspace": "1234567890123456",
"team": "9876543210987654",
"name": "Acme Corp - ACME-1042",
"notes": "Salesforce Project: https://your-domain.my.salesforce.com/a0X5e000000XYZ1EAO\n\nRepos:\n- Backend: https://github.com/blackboxprogramming/blackroad-ACME-1042-backend\n- Frontend: https://github.com/blackboxprogramming/blackroad-ACME-1042-frontend\n- Ops: https://github.com/blackboxprogramming/blackroad-ACME-1042-ops",
"color": "light-green",
"default_view": "board",
"public": false
}
}
```
**Response (201 Created):**
```json
{
"data": {
"gid": "5555555555555555",
"name": "Acme Corp - ACME-1042",
"permalink_url": "https://app.asana.com/0/5555555555555555/list"
}
}
```
**Salesforce Flow Implementation:**
```yaml
Element: HTTP Callout
Method: POST
Endpoint: {!$Credential.Asana_API}/projects
Headers:
- Authorization: Bearer {!$Credential.Asana_API.Token}
- Content-Type: application/json
Body:
{
"data": {
"workspace": "{!$CustomMetadata.Asana_Settings__mdt.Workspace_GID__c}",
"team": "{!$CustomMetadata.Asana_Settings__mdt.Team_GID__c}",
"name": "{!varProject.Name}",
"notes": "Salesforce: {!varProject.Id}\nRepos:\n- {!varProject.Backend_Repo_URL__c}",
"color": "light-green",
"default_view": "board"
}
}
Store Response: varAsanaResponse
Parse:
- varAsanaProjectGID = {!varAsanaResponse.data.gid}
- varAsanaProjectURL = {!varAsanaResponse.data.permalink_url}
Update Project Record:
- Asana_Project_URL__c = {!varAsanaProjectURL}
- Asana_Project_GID__c = {!varAsanaProjectGID}
```
---
### 2. Create Section
**Endpoint:**
```
POST https://app.asana.com/api/1.0/projects/{PROJECT_GID}/sections
```
**Payload:**
```json
{
"data": {
"name": "Discovery"
}
}
```
**Response (201 Created):**
```json
{
"data": {
"gid": "6666666666666666",
"name": "Discovery"
}
}
```
**Salesforce Implementation:**
```yaml
Element: Loop
Collection: ["Discovery", "Architecture", "Build", "Testing", "Go-Live"]
Current Item: varSectionName
Inside Loop:
- HTTP Callout
- Endpoint: {!$Credential.Asana_API}/projects/{!varAsanaProjectGID}/sections
- Body: {"data": {"name": "{!varSectionName}"}}
- Store Response: varSectionResponse
- Add to Collection: varSectionGIDs[{!varSectionName}] = {!varSectionResponse.data.gid}
```
---
### 3. Create Task
**Endpoint:**
```
POST https://app.asana.com/api/1.0/tasks
```
**Payload:**
```json
{
"data": {
"projects": ["5555555555555555"],
"name": "Confirm domain + DNS with client",
"notes": "Get final domain, subdomain, and DNS setup requirements from client.\n\nSalesforce Project: https://your-domain.my.salesforce.com/a0X5e000000XYZ1EAO",
"assignee": "3333333333333333",
"due_on": "2025-11-20",
"memberships": [
{
"project": "5555555555555555",
"section": "6666666666666666"
}
]
}
}
```
**Response (201 Created):**
```json
{
"data": {
"gid": "7777777777777777",
"name": "Confirm domain + DNS with client",
"permalink_url": "https://app.asana.com/0/5555555555555555/7777777777777777"
}
}
```
**Salesforce Implementation:**
```yaml
Element: Loop
Collection: varTaskDefinitions (custom metadata or JSON)
Current Item: varTask
Inside Loop:
- Calculate Due Date
Formula: TODAY() + {!varTask.DaysOffset}
- Lookup Assignee GID
From: Asana_User_Mapping__mdt
Match: Role = {!varTask.AssigneeRole}
- HTTP Callout
- Endpoint: {!$Credential.Asana_API}/tasks
- Body:
{
"data": {
"projects": ["{!varAsanaProjectGID}"],
"name": "{!varTask.Name}",
"notes": "{!varTask.Description}\n\nSalesforce: {!varProject.Id}",
"assignee": "{!varAssigneeGID}",
"due_on": "{!varDueDate}",
"memberships": [{
"project": "{!varAsanaProjectGID}",
"section": "{!varSectionGIDs[varTask.Section]}"
}]
}
}
```
---
### 4. Update Task (Mark Complete)
**Endpoint:**
```
PUT https://app.asana.com/api/1.0/tasks/{TASK_GID}
```
**Payload:**
```json
{
"data": {
"completed": true
}
}
```
**Response (200 OK):**
```json
{
"data": {
"gid": "7777777777777777",
"completed": true
}
}
```
---
### 5. Add Comment to Task
**Endpoint:**
```
POST https://app.asana.com/api/1.0/tasks/{TASK_GID}/stories
```
**Payload:**
```json
{
"data": {
"text": "✅ Deployed v0.1.3 to production\n\n**Commit:** a1b2c3d4\n**By:** github-user\n**Time:** 2025-11-17 14:30 UTC\n**Link:** https://github.com/org/repo/commit/a1b2c3d4"
}
}
```
**Response (201 Created):**
```json
{
"data": {
"gid": "8888888888888888",
"text": "✅ Deployed v0.1.3..."
}
}
```
---
## Task Template Definition
**Store in Salesforce Custom Metadata:** `Asana_Task_Template__mdt`
| Label | API Name | Section__c | Days_Offset__c | Assignee_Role__c |
|-------|----------|------------|----------------|------------------|
| Confirm domain + DNS | Confirm_Domain_DNS | Discovery | 1 | Sales Ops |
| Gather branding assets | Gather_Branding | Discovery | 1 | Design |
| Wire up Railway/Cloudflare | Wire_Up_Envs | Architecture | 3 | DevOps |
| Enable CI/CD secrets | Enable_Secrets | Architecture | 3 | DevOps |
| Set up database | Setup_Database | Build | 5 | Backend |
| Implement authentication | Implement_Auth | Build | 7 | Backend |
| Run E2E tests | Run_E2E_Tests | Testing | 12 | QA |
| Final client walkthrough | Final_Walkthrough | Go-Live | 18 | Customer Success |
| Deploy to production | Deploy_Production | Go-Live | 19 | DevOps |
**Query in Flow:**
```apex
[SELECT Label, Section__c, Days_Offset__c, Assignee_Role__c, Description__c
FROM Asana_Task_Template__mdt
ORDER BY Days_Offset__c ASC]
```
---
## Complete Salesforce Flow Example
**Flow Name:** `Asana_Project_Setup`
**Input Variables:**
- `ProjectRecordId` (Text)
- `AsanaProjectGID` (Text) - from Create Project step
**Steps:**
```yaml
1. Get Project Record
- Object: Project__c
- Filter: Id = {!ProjectRecordId}
2. Create Asana Project
- HTTP Callout (as documented)
- Store: varAsanaProjectGID
3. Create Sections
- Loop: ["Discovery", "Architecture", "Build", "Testing", "Go-Live"]
- HTTP Callout per section
- Store GIDs in Map: varSectionGIDs
4. Get Task Templates
- Get Records: Asana_Task_Template__mdt
- Store: varTaskTemplates
5. Loop: Create Tasks
- For Each: varTaskTemplates
- Calculate due date: TODAY() + DaysOffset
- Lookup assignee GID from Asana_User_Mapping__mdt
- HTTP Callout to create task
- Link task to correct section
6. Update Project Record
- Asana_Project_URL__c = {!varAsanaResponse.data.permalink_url}
- Asana_Project_GID__c = {!varAsanaProjectGID}
```
---
## User Mapping Setup
**Custom Metadata Type:** `Asana_User_Mapping__mdt`
**Fields:**
- `Role__c` (Text) - "DevOps", "Backend", "Customer Success", etc.
- `Email__c` (Email)
- `Asana_GID__c` (Text)
**Records:**
| Label | Role__c | Email__c | Asana_GID__c |
|-------|---------|----------|--------------|
| DevOps Team | DevOps | devops@blackroad.com | 2222222222222222 |
| Backend Team | Backend | backend@blackroad.com | 1111111111111111 |
| Customer Success | Customer Success | brenda@blackroad.com | 3333333333333333 |
| QA Team | QA | qa@blackroad.com | 4444444444444444 |
**Query in Flow:**
```apex
[SELECT Asana_GID__c
FROM Asana_User_Mapping__mdt
WHERE Role__c = :assigneeRole
LIMIT 1]
```
---
## Error Handling
### Common Errors
| Status Code | Error | Cause | Solution |
|-------------|-------|-------|----------|
| 400 | Bad Request | Invalid GID or payload | Verify workspace/team GIDs |
| 401 | Unauthorized | Invalid token | Regenerate PAT in Asana |
| 403 | Forbidden | No access to workspace | Check PAT has workspace access |
| 404 | Not Found | Project/task doesn't exist | Verify GID is correct |
| 429 | Too Many Requests | Rate limit exceeded | Implement exponential backoff |
### Salesforce Fault Path
```yaml
Fault Path:
- Element: Create Case
Subject: "Asana Integration Error: {!$Flow.FaultMessage}"
Description: "Failed to create Asana project for: {!varProject.Name}\n\nError: {!$Flow.FaultMessage}"
Priority: Medium
Type: "Automation Bug"
- Element: Send Email
To: ops@blackroad.com
Subject: "Asana Automation Failed - Manual Project Needed"
Body: "Project: {!varProject.Name}\nKey: {!varProject.Project_Key__c}\n\nPlease create Asana project manually."
```
---
## Rate Limits
**Asana API Rate Limits:**
- 1,500 requests per minute per user
- Burst: Up to 60 requests in the first second
**Per Asana Project Creation:**
- 1 request: Create project
- 5 requests: Create sections
- 8-10 requests: Create tasks
- **Total:** ~15 requests
**Best Practices:**
- Can create ~100 projects per minute
- Add delay between operations if hitting limits
- Use exponential backoff on 429 errors
---
## Testing
### Manual Test (curl)
```bash
# 1. Create project
curl -X POST "https://app.asana.com/api/1.0/projects" \
-H "Authorization: Bearer YOUR_PAT" \
-H "Content-Type: application/json" \
-d '{
"data": {
"workspace": "1234567890123456",
"name": "Test Project - DELETE ME"
}
}'
# Response: Get project GID (e.g., 5555555555555555)
# 2. Create section
curl -X POST "https://app.asana.com/api/1.0/projects/5555555555555555/sections" \
-H "Authorization: Bearer YOUR_PAT" \
-d '{"data": {"name": "To Do"}}'
# 3. Create task
curl -X POST "https://app.asana.com/api/1.0/tasks" \
-H "Authorization: Bearer YOUR_PAT" \
-d '{
"data": {
"projects": ["5555555555555555"],
"name": "Test task"
}
}'
# 4. Clean up - delete project
curl -X DELETE "https://app.asana.com/api/1.0/projects/5555555555555555" \
-H "Authorization: Bearer YOUR_PAT"
```
### Salesforce Sandbox Test
1. Create test Project record
2. Run flow: `Asana_Project_Setup`
3. Verify:
- Project created in Asana
- Sections present
- Tasks created with correct assignees
- Due dates calculated correctly
4. Clean up test project in Asana
---
## Monitoring
**Track These Metrics:**
| Metric | Target | Alert Threshold |
|--------|--------|-----------------|
| Asana API Success Rate | > 98% | < 95% |
| Avg Project Creation Time | < 30s | > 60s |
| Failed Project Creations | < 2% | > 5% |
| Tasks Created Per Project | ~8-10 | < 5 or > 15 |
**Salesforce Custom Object:** `Asana_API_Log__c`
**Fields:**
- Operation__c (Create Project, Create Task, etc.)
- Project__c (Lookup)
- Status__c (Success, Failed)
- Response_Time__c
- Error_Message__c
- Timestamp__c
---
## Security Best Practices
1. **PAT Security:**
- Never share PAT
- Use encrypted custom settings
- Rotate every 90 days
- Generate separate PAT per integration (if multiple)
2. **Project Visibility:**
- Set `public: false` for client projects
- Only share with relevant team members
- Review permissions quarterly
3. **Audit Logging:**
- Log all Asana API calls in Salesforce
- Review monthly
- Track who accessed projects
---
## Related Documentation
- [Asana API Docs: Projects](https://developers.asana.com/docs/projects)
- [Asana API Docs: Tasks](https://developers.asana.com/docs/tasks)
- [Salesforce Orchestration: New Client Kickoff](../salesforce/orchestrations/new-client-kickoff-orchestration.md)
- [Workflow: New Client Kickoff](../workflows/new-client-kickoff.md)
- [Integration: Salesforce → GitHub](./salesforce-to-github.md)
---
## Changelog
| Date | Version | Change | Author |
|------|---------|--------|--------|
| 2025-11-17 | 1.0 | Initial specification | Cece (Claude) |

View File

@@ -0,0 +1,539 @@
# Integration: Salesforce → GitHub
**Purpose:** Enable Salesforce to create and configure GitHub repositories automatically
**Direction:** Salesforce calls GitHub REST API
**Authentication:** GitHub App or Personal Access Token
**Status:** Active
**Last Updated:** 2025-11-17
---
## Overview
This integration allows Salesforce Flows and Orchestrations to:
- Create new repositories
- Configure repository settings
- Add labels, branch protection, workflows
- Manage repository secrets
**Key Use Case:** Automatic repo creation when Opportunity moves to "Closed Won"
---
## Authentication Setup
### Option A: GitHub App (Recommended for Production)
**Benefits:**
- More secure (short-lived tokens)
- Better rate limits
- Granular permissions
- Audit trail
**Setup Steps:**
1. **Create GitHub App:**
- Go to: https://github.com/organizations/blackboxprogramming/settings/apps
- Click "New GitHub App"
- Name: `BlackRoad Salesforce Integration`
- Homepage URL: `https://blackroad.app`
- Webhook URL: (leave blank for now, covered in github-to-salesforce.md)
2. **Permissions:**
- Repository permissions:
- Administration: Read & Write
- Contents: Read & Write
- Metadata: Read-only
- Secrets: Read & Write
- Workflows: Read & Write
- Organization permissions:
- Members: Read-only
3. **Install App:**
- Install app on organization: `blackboxprogramming`
- Select: All repositories (or specific repos)
4. **Generate Private Key:**
- Download private key (`.pem` file)
- Store securely in Salesforce
5. **Get App Details:**
- App ID: `123456`
- Installation ID: `789012`
**Salesforce Named Credential:**
- Name: `GitHub_API`
- URL: `https://api.github.com`
- Identity Type: Named Principal
- Authentication Protocol: Custom
- Custom Authentication: Use Apex class to generate JWT → exchange for installation access token
**Apex Class for Token Generation:**
```apex
public class GitHubAppTokenProvider {
private static final String GITHUB_APP_ID = '123456';
private static final String GITHUB_APP_PRIVATE_KEY = 'YOUR_PRIVATE_KEY_HERE'; // Store in Protected Custom Setting
public static String getInstallationToken() {
// 1. Generate JWT
String jwt = generateJWT();
// 2. Exchange JWT for installation access token
HttpRequest req = new HttpRequest();
req.setEndpoint('https://api.github.com/app/installations/789012/access_tokens');
req.setMethod('POST');
req.setHeader('Authorization', 'Bearer ' + jwt);
req.setHeader('Accept', 'application/vnd.github+json');
Http http = new Http();
HttpResponse res = http.send(req);
if (res.getStatusCode() == 201) {
Map<String, Object> result = (Map<String, Object>) JSON.deserializeUntyped(res.getBody());
return (String) result.get('token');
}
throw new CalloutException('Failed to get GitHub access token: ' + res.getBody());
}
private static String generateJWT() {
// Use JWT library or implement JWT generation
// See: https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/generating-a-json-web-token-jwt-for-a-github-app
}
}
```
---
### Option B: Personal Access Token (Quick Start / Testing)
**Setup Steps:**
1. **Create PAT:**
- Go to: https://github.com/settings/tokens
- Click "Generate new token (classic)"
- Scopes:
- `repo` (full control)
- `workflow` (update workflows)
- `admin:org` (read org)
2. **Store in Salesforce:**
- Setup → Named Credentials → New
- Name: `GitHub_API`
- URL: `https://api.github.com`
- Identity Type: Named Principal
- Authentication Protocol: Password Authentication
- Username: (your GitHub username)
- Password: (paste PAT)
**⚠️ Security Note:** PAT never expires unless you set an expiration. For production, use GitHub App.
---
## API Endpoints & Payloads
### 1. Create Repository
**Endpoint:**
```
POST https://api.github.com/orgs/blackboxprogramming/repos
```
**Headers:**
```
Authorization: Bearer {GITHUB_TOKEN}
Accept: application/vnd.github+json
X-GitHub-Api-Version: 2022-11-28
Content-Type: application/json
```
**Payload:**
```json
{
"name": "blackroad-ACME-1042-backend",
"description": "Backend for Acme Corp (ACME-1042)",
"private": true,
"auto_init": true,
"gitignore_template": "Python",
"license_template": "mit"
}
```
**Response (201 Created):**
```json
{
"id": 123456789,
"name": "blackroad-ACME-1042-backend",
"full_name": "blackboxprogramming/blackroad-ACME-1042-backend",
"html_url": "https://github.com/blackboxprogramming/blackroad-ACME-1042-backend",
"clone_url": "https://github.com/blackboxprogramming/blackroad-ACME-1042-backend.git",
"default_branch": "main",
...
}
```
**Salesforce Flow Implementation:**
```yaml
Element: HTTP Callout
Method: POST
Endpoint: {!$Credential.GitHub_API}/orgs/blackboxprogramming/repos
Headers:
- Authorization: Bearer {!$Credential.GitHub_API.AccessToken}
- Accept: application/vnd.github+json
- Content-Type: application/json
Body: (JSON from above, with merge fields)
Store Response In: varRepoResponse
Parse:
- varRepoURL = {!varRepoResponse.html_url}
- varRepoName = {!varRepoResponse.name}
```
---
### 2. Create Labels
**Endpoint:**
```
POST https://api.github.com/repos/blackboxprogramming/{REPO_NAME}/labels
```
**Payload (repeat for each label):**
```json
{
"name": "type:feature",
"color": "0E8A16",
"description": "New feature or enhancement"
}
```
**Salesforce Implementation:**
Use a loop to create multiple labels from a JSON dataset:
```yaml
Element: Loop
Collection: Parse JSON from sop/templates/repo-template/.github/labels.json
Current Item: varLabel
Inside Loop:
- HTTP Callout
- Endpoint: {!$Credential.GitHub_API}/repos/blackboxprogramming/{!varRepoName}/labels
- Body: {!varLabel}
```
---
### 3. Apply Branch Protection
**Endpoint:**
```
PUT https://api.github.com/repos/blackboxprogramming/{REPO_NAME}/branches/main/protection
```
**Payload:**
```json
{
"required_status_checks": {
"strict": true,
"contexts": ["test (3.11)", "test (3.12)", "lint", "build"]
},
"enforce_admins": true,
"required_pull_request_reviews": {
"dismiss_stale_reviews": true,
"require_code_owner_reviews": true,
"required_approving_review_count": 1
},
"restrictions": null,
"required_linear_history": true,
"allow_force_pushes": false,
"allow_deletions": false
}
```
**Response (200 OK):**
```json
{
"url": "https://api.github.com/repos/blackboxprogramming/blackroad-ACME-1042-backend/branches/main/protection",
...
}
```
---
### 4. Create Workflow Files
**Endpoint:**
```
PUT https://api.github.com/repos/blackboxprogramming/{REPO_NAME}/contents/.github/workflows/ci.yml
```
**Payload:**
```json
{
"message": "Add CI workflow",
"content": "{BASE64_ENCODED_CI_YML}",
"branch": "main"
}
```
**Salesforce Implementation:**
1. Store workflow YAML templates in Salesforce Static Resources:
- `ci_yml`
- `deploy_yml`
- `safety_yml`
2. For each workflow:
- Load static resource
- Base64 encode content (use Apex)
- PUT to GitHub
**Apex Helper:**
```apex
public static String base64EncodeStaticResource(String resourceName) {
StaticResource sr = [SELECT Body FROM StaticResource WHERE Name = :resourceName LIMIT 1];
return EncodingUtil.base64Encode(sr.Body);
}
```
---
### 5. Add Repository Secrets
**Endpoint:**
```
PUT https://api.github.com/repos/blackboxprogramming/{REPO_NAME}/actions/secrets/{SECRET_NAME}
```
**Payload:**
```json
{
"encrypted_value": "{ENCRYPTED_SECRET}",
"key_id": "{PUBLIC_KEY_ID}"
}
```
**Pre-requisite:** Get repository public key
```
GET https://api.github.com/repos/blackboxprogramming/{REPO_NAME}/actions/secrets/public-key
```
**Response:**
```json
{
"key_id": "012345678912345678",
"key": "BASE64_PUBLIC_KEY"
}
```
**Encryption:**
Use libsodium sealed boxes (NaCl) to encrypt secrets.
**Apex Implementation:** Use external service or pre-encrypted values.
**Secrets to Add:**
| Secret Name | Value Source |
|-------------|--------------|
| `PROJECT_KEY` | From Project__c.Project_Key__c |
| `SALESFORCE_INSTANCE_URL` | From Salesforce |
| `SALESFORCE_ACCESS_TOKEN` | Generate Connected App token |
| `RAILWAY_TOKEN` | From Salesforce Custom Setting |
| `CLOUDFLARE_API_TOKEN` | From Salesforce Custom Setting |
| `ASANA_PAT` | From Salesforce Custom Setting |
---
## Complete Flow Example
**Salesforce Flow: Create GitHub Repos for Project**
```yaml
Flow Name: GitHub_Repo_Setup
Input Variables:
- ProjectRecordId (Text)
Steps:
1. Get Project Record
- Object: Project__c
- Filter: Id = {!ProjectRecordId}
- Store: varProject
2. Loop: For Each Repo Type
- Collection: ["backend", "frontend", "ops"]
- Current Item: varRepoType
2.1: Create Repo
- HTTP Callout (as documented above)
- Store Response: varRepoResponse
2.2: Create Labels
- Loop through labels.json
- HTTP Callout for each label
2.3: Apply Branch Protection
- HTTP Callout (as documented)
2.4: Create Workflow Files
- For each: ci.yml, deploy.yml, safety.yml
- HTTP Callout to create file
2.5: Add Secrets
- Get public key
- Encrypt secrets
- PUT each secret
2.6: Update Project Record
- Assignment:
- Backend_Repo_URL__c = {!varRepoResponse.html_url} (if backend)
- Frontend_Repo_URL__c = {!varRepoResponse.html_url} (if frontend)
- Ops_Repo_URL__c = {!varRepoResponse.html_url} (if ops)
3. Update Project Record with all URLs
- Update Record: Project__c
4. Send Success Notification
- Post to Chatter or send email
```
---
## Error Handling
### Common Errors
| Status Code | Error | Cause | Solution |
|-------------|-------|-------|----------|
| 401 | Unauthorized | Invalid token | Refresh GitHub App token or regenerate PAT |
| 403 | Forbidden | Insufficient permissions | Check GitHub App/PAT scopes |
| 422 | Unprocessable Entity | Repo name already exists | Check for existing repo first |
| 422 | Validation Failed | Branch protection: required check doesn't exist | Create workflow first, then apply protection |
| 404 | Not Found | Repo or resource doesn't exist | Verify repo was created successfully |
### Salesforce Fault Path
```yaml
Fault Path:
- Element: Create Case
Subject: "GitHub Integration Error: {!$Flow.FaultMessage}"
Description: "Failed to create GitHub repo for Project: {!varProject.Name}\n\nError: {!$Flow.FaultMessage}"
Priority: High
OwnerId: {!DevOpsQueueId}
- Element: Send Email
To: devops@blackroad.com
Subject: "GitHub Automation Failed"
Body: (include error details)
```
---
## Testing
### Manual Test (Postman / curl)
```bash
# 1. Create repo
curl -X POST \
https://api.github.com/orgs/blackboxprogramming/repos \
-H "Authorization: token YOUR_PAT" \
-H "Accept: application/vnd.github+json" \
-d '{
"name": "test-repo-delete-me",
"private": true,
"auto_init": true
}'
# 2. Create label
curl -X POST \
https://api.github.com/repos/blackboxprogramming/test-repo-delete-me/labels \
-H "Authorization: token YOUR_PAT" \
-d '{
"name": "type:test",
"color": "BADA55"
}'
# 3. Clean up
curl -X DELETE \
https://api.github.com/repos/blackboxprogramming/test-repo-delete-me \
-H "Authorization: token YOUR_PAT"
```
### Salesforce Sandbox Test
1. Create test Project record
2. Run flow: `GitHub_Repo_Setup` with test Project ID
3. Verify:
- Repos created in GitHub
- Labels applied
- Branch protection enabled
- Workflows present
- Secrets added
4. Clean up test repos
---
## Rate Limits
**GitHub API Rate Limits:**
- PAT: 5,000 requests/hour
- GitHub App: 15,000 requests/hour
**Per Repository Creation:**
- Approximately 50-100 API calls (1 repo + labels + protection + workflows + secrets)
- Can create ~50-300 repos per hour
**Best Practices:**
- Use GitHub App for better limits
- Implement exponential backoff on 403 (rate limit exceeded)
- Cache public keys for secret encryption
- Batch operations where possible
---
## Monitoring
**Track These Metrics:**
| Metric | Target | Alert Threshold |
|--------|--------|-----------------|
| API Success Rate | > 98% | < 95% |
| Avg Response Time | < 2s | > 5s |
| Rate Limit Usage | < 50% | > 80% |
| Failed Repo Creations | < 2% | > 5% |
**Salesforce Custom Object:** `GitHub_API_Log__c`
**Fields:**
- Operation__c (Create Repo, Add Label, etc.)
- Project__c (Lookup)
- Status__c (Success, Failed)
- Status_Code__c (200, 201, 422, etc.)
- Error_Message__c
- Response_Time__c
- Timestamp__c
---
## Related Documentation
- [GitHub API Docs: Repositories](https://docs.github.com/en/rest/repos/repos)
- [GitHub API Docs: Branch Protection](https://docs.github.com/en/rest/branches/branch-protection)
- [GitHub API Docs: Secrets](https://docs.github.com/en/rest/actions/secrets)
- [Salesforce Orchestration: New Client Kickoff](../salesforce/orchestrations/new-client-kickoff-orchestration.md)
- [Workflow: New Client Kickoff](../workflows/new-client-kickoff.md)
---
## Changelog
| Date | Version | Change | Author |
|------|---------|--------|--------|
| 2025-11-17 | 1.0 | Initial specification | Cece (Claude) |

View File

@@ -0,0 +1,347 @@
# New Client Kickoff Checklist (Brenda Edition)
**For:** Operations, Customer Success, Account Managers
**Time:** 30 minutes of your time, 5-10 minutes of robot time
**Last Updated:** 2025-11-17
---
## What This Is
This is your **simple, step-by-step guide** for onboarding a new client after a deal closes. Most of the work happens automatically, but you need to:
1. Make sure everything kicks off correctly
2. Fill in a few details the robots can't know
3. Verify everything worked
4. Communicate with the customer
**No technical knowledge required.** If something breaks, there are clear instructions for who to ask for help.
---
## Before You Start
You'll need:
- [ ] Access to Salesforce
- [ ] Access to Asana
- [ ] Client's primary contact info (name, email, phone)
- [ ] Client's domain preference (if custom)
- [ ] Package type they purchased (OS / Console / Custom)
---
## The Checklist
### 📋 **Part 1: Mark the Deal Closed (Day 0)**
**Where:** Salesforce
1. Open the **Opportunity** record for this deal
2. Click **Edit**
3. Change **Stage** to: `Closed Won`
4. Fill in these fields if they're empty:
- **Account Name:** Company name
- **Primary Contact:** Main person at the company
- **Domain/Subdomain:** What they want their site to be called (e.g., `acme-portal`)
- **Package Type:** OS / Console / Custom (what they bought)
- **Service Tier:** Starter / Pro / Enterprise
- **Start Date:** Today (or agreed start date)
5. Click **Save**
**What happens next (automatically):**
- A **Project** record gets created in Salesforce (within 2 minutes)
- Repos appear in GitHub (within 5-10 minutes)
- An **Asana project** gets created with all tasks (within 5-10 minutes)
- You get a **Slack notification** in #ops saying everything is ready
**⏱️ Wait 10 minutes**, then move to Part 2.
---
### ✅ **Part 2: Verify the Magic Happened (Day 0)**
**Where:** Salesforce → Project record
1. Go back to the **Opportunity** in Salesforce
2. Scroll down to **Related****Projects**
3. You should see a new **Project** record with a name like `ACME-X7K9`
**Click into the Project record and check:**
- [ ] **Project Key** is filled in (e.g., `ACME-X7K9`)
- [ ] **Status** says: `Setup In Progress`
- [ ] **Backend Repo URL** is filled in (a GitHub link)
- [ ] **Frontend Repo URL** is filled in (a GitHub link)
- [ ] **Ops Repo URL** is filled in (a GitHub link)
- [ ] **Asana Project URL** is filled in (an Asana link)
**If any of these are empty after 15 minutes:**
→ Something broke. Skip to "Troubleshooting" section below.
**If everything is filled in:**
→ 🎉 You're golden! Move to Part 3.
---
### 📝 **Part 3: Add Client-Specific Details (Day 0)**
**Where:** Asana
1. Click the **Asana Project URL** from the Salesforce Project record (opens Asana)
2. You should see a project with sections: Discovery, Architecture, Build, Testing, Go-Live
3. Go to the **Discovery** section
4. Click the task: **"Confirm domain + DNS with client"**
5. Assign it to **yourself**
6. Add a comment with:
- Client's preferred domain/subdomain
- Any special requests (custom branding, integrations, etc.)
- Primary contact info
7. Click the task: **"Gather branding assets"**
8. Assign it to **Design team**
9. Add a comment with:
- "Client: [Company Name]"
- "Contact: [Name, Email]"
- "Need: Logo, brand colors, fonts"
**Where:** Salesforce Project
1. Go back to the **Project** record in Salesforce
2. Click **Edit**
3. Fill in:
- **Primary Contact:** Lookup the Contact record
- **Technical Owner:** Assign to a developer (or leave blank for now)
- **Customer Notes:** Any special context about this client
4. Click **Save**
---
### 📧 **Part 4: Notify the Client (Day 0-1)**
**Where:** Email / Phone
Send the client a friendly kickoff message:
**Template:**
> Subject: Welcome to BlackRoad! Your Project is Live 🚀
>
> Hi [Client Name],
>
> Great news! We've kicked off your project. Here's what's happening:
>
> **Your Project:** [Project Key, e.g., ACME-X7K9]
> **Package:** [OS / Console / Custom]
> **Next Steps:**
> 1. Our team will reach out within 24 hours to confirm your domain and branding.
> 2. We'll send you a staging link within 3-5 business days for initial review.
> 3. Your primary point of contact is [Technical Owner Name] for technical questions, and me for everything else.
>
> **Need anything?**
> Just reply to this email or call me at [your number].
>
> Looking forward to building something great together!
>
> [Your Name]
> [Your Title]
> BlackRoad
---
### 👀 **Part 5: Monitor Progress (Ongoing)**
**Where:** Asana (check daily)
1. Open the **Asana project** for this client
2. Look at tasks:
-**Green checkmarks** = Done automatically or by the team
-**In Progress** = Someone's working on it
- 🔴 **Overdue** = Needs attention
**What to watch for:**
- Tasks marked "blocked" → Check the comments, see what's blocking, help unblock
- Tasks overdue by > 2 days → Ping the assignee in Slack or Asana
- Client reaches out with questions → Add a task in Asana under "Discovery" section
**Where:** Salesforce Project (check weekly)
1. Open the **Project** record
2. Look at **Last Deploy At** field:
- Should update every few days (means engineers are deploying)
- If it's been > 1 week with no deploys → Ask in #ops "Is [Project Key] blocked?"
---
### 🎯 **Part 6: Final Review & Go-Live (Day 14-30)**
**Where:** Asana → "Go-Live" section
When you see the task **"Final client walkthrough"** assigned to you:
1. Schedule a 30-minute call with the client
2. Walk them through their staging environment
3. Get their final approval:
- [ ] Design looks good
- [ ] Functionality works as expected
- [ ] They're ready to go live
4. In the Asana task, add a comment: "Client approved on [Date]. Ready for production."
5. Assign the task **"Deploy to production"** to **DevOps**
**What happens next (automatically):**
- Engineers deploy to production
- Your Asana task auto-completes when deploy succeeds
- Salesforce updates with production URL
- You get a Slack notification: "✅ [Project Key] is live!"
**Where:** Email the client
Send the go-live notification:
> Subject: You're Live! 🎉
>
> Hi [Client Name],
>
> Exciting news your BlackRoad site is now **live in production**!
>
> **Your URL:** https://[their-domain].blackroad.app
> **Login credentials:** [sent separately via secure method]
>
> **What's next:**
> - We'll monitor the site 24/7 for the first week
> - If you notice anything unusual, email support@blackroad.com or reply here
> - We'll check in with you in 1 week to see how things are going
>
> Congratulations! 🚀
>
> [Your Name]
---
## Troubleshooting
### Problem: "Project record was created, but repos/Asana are empty after 15 minutes"
**Fix:**
1. Go to the **Project** record in Salesforce
2. Copy the **Project Key** (e.g., `ACME-X7K9`)
3. Go to GitHub: https://github.com/blackboxprogramming
4. Search for repos with that Project Key in the name
5. **If repos exist but URLs aren't in Salesforce:**
- Manually copy the repo URLs into the Salesforce Project fields
- Post in #ops: "Automation hiccup for [Project Key] repos created but didn't sync to Salesforce"
6. **If repos DON'T exist:**
- Post in #ops: "Urgent: GitHub repos not created for [Project Key]. Need manual setup."
- Tag @devops
---
### Problem: "Asana project was never created"
**Fix:**
1. Manually create an Asana project:
- Go to Asana
- Click **+ New Project**
- Name it: `[Account Name] - [Project Key]`
- Choose **Board** view
2. Copy the project URL
3. Paste it into the Salesforce **Project** record → **Asana Project URL** field
4. Add these sections manually:
- Discovery
- Architecture
- Build
- Testing
- Go-Live
5. Post in #ops: "Asana automation failed for [Project Key] created manually"
---
### Problem: "Engineers are asking me technical questions I don't understand"
**Fix:**
1. **Don't guess.** It's okay to say "I don't know, let me find out."
2. Ask the client for clarification
3. Post the question + client's answer in the **Asana project** under the relevant task
4. Tag the engineer who asked
---
### Problem: "Client is frustrated / things are taking too long"
**Fix:**
1. Look at the **Asana project** → find which tasks are overdue
2. Post in #ops: "[Project Key] is delayed [Task Name] is overdue. Can someone help?"
3. Schedule a call with the client to explain:
- What's blocking us
- New timeline
- What we're doing to unblock
4. Follow up in **Salesforce** → Project record → add a note in **Customer Notes**
---
## Pro Tips
**Tip 1: Check Asana every morning**
Spend 5 minutes scanning all your active client projects. Catch issues early.
**Tip 2: Use Slack for quick questions**
If a task is blocked, post in #ops or #dev with the Asana task link. Much faster than email.
**Tip 3: Keep clients in the loop**
Send a quick "Hey, we deployed X this week" update every Friday. Clients love visibility.
**Tip 4: Use the Salesforce Activity feed**
Log every client call, email, or decision in Salesforce. Future-you will thank you.
**Tip 5: Trust the automation**
The robots are good at their job. If something doesn't auto-complete, it's probably because it's waiting on something manual (like your approval or client input). Check the task comments.
---
## Quick Reference
| I need to... | Go here... |
|-------------|-----------|
| Mark a deal closed | Salesforce → Opportunity → Change Stage to "Closed Won" |
| Check if automation worked | Salesforce → Project record → Check if URLs are filled |
| See what tasks need doing | Asana → Open the project for that client |
| Find GitHub repos | Salesforce → Project record → Click the repo URL links |
| See latest deploys | Salesforce → Project record → "Last Deploy At" field |
| Report a broken automation | Slack → #ops → Tag @devops |
| Ask a technical question | Slack → #dev → Include Asana task link |
---
## Who to Ask for Help
| If you need... | Ask... | Where... |
|---------------|--------|----------|
| Automation isn't working | @devops | Slack #ops |
| Task is blocked / unclear | The task assignee | Asana comment or Slack |
| Client has technical questions | @technical-owner (from Salesforce Project) | Slack or tag in Asana |
| Client is unhappy / escalation | Your manager | Slack DM or meeting |
---
## Remember
**You are the glue between the client and the robots.**
Your job is NOT to understand how GitHub Actions work or what a "CI pipeline" is.
Your job IS to:
- Make sure clients feel heard and informed
- Catch things that fall through the cracks
- Keep Asana and Salesforce up to date with client context
- Escalate technical issues to technical people
**The system is designed to make your life easier.** If it's not, tell us and we'll fix it.
---
**Questions?**
Post in #ops or email ops@blackroad.com

View File

@@ -0,0 +1,463 @@
# Salesforce Flow: Opportunity Automation on Stage Change
**API Name:** `Opp_Automation_OnStageChange`
**Type:** Record-Triggered Flow (After Save)
**Object:** Opportunity
**Status:** Active
**Last Updated:** 2025-11-17
---
## Purpose
This flow automates the creation of Project records and kickoff of the orchestration when an Opportunity moves to "Closed Won" stage.
**Business Value:**
- Eliminates manual project setup
- Ensures consistency across all new clients
- Triggers downstream automation (GitHub, Asana)
- Captures all required metadata at deal closure
---
## Trigger Configuration
### Object
`Opportunity`
### Trigger Type
**Record-Triggered Flow (After Save)**
### Trigger Criteria
**When:** A record is updated
**Condition Requirements:**
- **StageName** changed
- **StageName** equals "Closed Won"
**Entry Condition Logic:**
```
AND(
ISCHANGED(StageName),
TEXT(StageName) = "Closed Won"
)
```
### Optimization
- ✅ Run asynchronously (Fast Field Updates) - **No** (need to create related records)
- ✅ Trigger order - **1** (run first, before other flows)
---
## Flow Variables
Create these variables at the start:
| Variable Name | Type | Default Value | Description |
|---------------|------|---------------|-------------|
| `varProjectKey` | Text | null | Generated project key (e.g., ACME-X7K9) |
| `varAccountSlug` | Text | null | Account name converted to slug |
| `varRandomSuffix` | Text | null | 4-character random suffix |
| `varProjectRecord` | Record (Project__c) | null | Created project record |
| `varExistingProject` | Record Collection (Project__c) | null | Check for existing project |
---
## Flow Logic
### 1. Decision: Validate Required Fields
**Element Type:** Decision
**Criteria:**
Check if all required fields are populated:
```
AND(
NOT(ISBLANK({!$Record.AccountId})),
NOT(ISBLANK({!$Record.Primary_Domain__c})),
NOT(ISBLANK({!$Record.Package_Type__c})),
NOT(ISBLANK({!$Record.Service_Tier__c}))
)
```
**Outcomes:**
- **Valid** → Continue to next step
- **Invalid** → Send error notification and stop
---
### 2A. Path: Invalid - Send Error Notification
**Element Type:** Action - Send Email
**To:** Opportunity Owner
**Subject:** `Action Required: Opportunity {!$Record.Name} Missing Information`
**Body:**
```
The opportunity "{!$Record.Name}" was marked as Closed Won, but is missing required information:
Required Fields:
- Account
- Primary Domain
- Package Type
- Service Tier
Please update the opportunity and change the stage to "Closed Won" again to trigger automation.
Opportunity Link: {!$Record.Id}
```
**Then:** Stop
---
### 2B. Path: Valid - Continue
#### Step 2.1: Check for Existing Project
**Element Type:** Get Records
**Object:** `Project__c`
**Conditions:**
- `Opportunity__c` equals `{!$Record.Id}`
**Store Output:** `varExistingProject`
---
#### Step 2.2: Decision: Project Already Exists?
**Criteria:**
```
NOT(ISNULL({!varExistingProject}))
```
**Outcomes:**
- **Exists** → Skip creation, update existing
- **Does Not Exist** → Create new project
---
#### Step 2.3A: Path: Project Exists - Update It
**Element Type:** Update Records
**Record:** `{!varExistingProject[0]}`
**Fields to Update:**
- `Status__c` = "Setup In Progress"
- `Updated_From_Opp__c` = `TODAY()`
- `Service_Tier__c` = `{!$Record.Service_Tier__c}`
- `Package_Type__c` = `{!$Record.Package_Type__c}`
**Then:** Skip to Step 3 (Launch Orchestration)
---
#### Step 2.3B: Path: Project Does Not Exist - Create It
##### Step 2.3B.1: Generate Account Slug
**Element Type:** Assignment
**Variable:** `varAccountSlug`
**Operator:** Equals
**Value:** Formula:
```
SUBSTITUTE(
SUBSTITUTE(
SUBSTITUTE(
UPPER(LEFT({!$Record.Account.Name}, 10)),
" ", "-"
),
".", ""
),
",", ""
)
```
**Purpose:** Convert "Acme Corp." → "ACME-CORP"
---
##### Step 2.3B.2: Generate Random Suffix
**Element Type:** Assignment
**Variable:** `varRandomSuffix`
**Value:** Formula:
```
SUBSTITUTE(
LEFT(TEXT(RAND()), 4),
"0.", ""
) & TEXT(FLOOR(RAND() * 10000))
```
**Note:** Generates a 4-character alphanumeric suffix (simplified version; in production, use a more robust random generator or auto-number)
**Better Alternative:** Use Salesforce auto-number field on Project__c:
- Field: `Project_Number__c` (Auto Number)
- Format: `{0000}`
- Starting Number: `1000`
---
##### Step 2.3B.3: Build Project Key
**Element Type:** Assignment
**Variable:** `varProjectKey`
**Value:**
```
{!varAccountSlug} & "-" & TEXT({!$Record.Project_Number__c})
```
**Example Output:** `ACME-1042`
---
##### Step 2.3B.4: Create Project Record
**Element Type:** Create Records
**Object:** `Project__c`
**Fields:**
| Field API Name | Value |
|----------------|-------|
| `Name` | `{!$Record.Account.Name} - {!varProjectKey}` |
| `Project_Key__c` | `{!varProjectKey}` |
| `Account__c` | `{!$Record.AccountId}` |
| `Opportunity__c` | `{!$Record.Id}` |
| `Primary_Domain__c` | `{!$Record.Primary_Domain__c}` |
| `Package_Type__c` | `{!$Record.Package_Type__c}` |
| `Service_Tier__c` | `{!$Record.Service_Tier__c}` |
| `Start_Date__c` | `TODAY()` |
| `Status__c` | `"Setup In Progress"` |
| `Technical_Owner__c` | `{!$Record.OwnerId}` (or default to a user) |
| `OwnerId` | `{!$Record.OwnerId}` |
**Store Output:** `varProjectRecord`
---
### 3. Launch Orchestration
**Element Type:** Action - Orchestration
**Orchestration Name:** `New_Client_Kickoff_Orchestration`
**Input Variables:**
- `ProjectRecordId` = `{!varProjectRecord.Id}`
- `OpportunityRecordId` = `{!$Record.Id}`
- `AccountRecordId` = `{!$Record.AccountId}`
**Run Asynchronously:** Yes
---
### 4. Send Notification to Ops Team
**Element Type:** Action - Post to Chatter / Slack
**Option A: Chatter Post**
```
New project created! 🚀
Project: {!varProjectRecord.Name}
Project Key: {!varProjectRecord.Project_Key__c}
Account: {!$Record.Account.Name}
Package: {!$Record.Package_Type__c}
Orchestration started. GitHub repos and Asana project will be created within 5-10 minutes.
Link: {!varProjectRecord.Id}
```
**Option B: Slack (via HTTP Callout)**
See [Integration: Salesforce → Slack](../../integrations/salesforce-to-slack.md)
---
### 5. Update Opportunity with Project Link
**Element Type:** Update Records
**Record:** `{!$Record}`
**Fields to Update:**
- `Project__c` (Lookup field) = `{!varProjectRecord.Id}`
---
## Error Handling
### Fault Path
If any step fails:
**Element Type:** Action - Create Case
**Fields:**
- `Subject` = `"Automation Error: {!$Record.Name} Project Creation Failed"`
- `Description` = `"Error creating project for opportunity: {!$Record.Id}\n\nError: {!$Flow.FaultMessage}"`
- `Priority` = `"High"`
- `Type` = `"Automation Bug"`
- `OwnerId` = `<DevOps Queue ID>`
**Then:** Send email to DevOps team
---
## Testing Checklist
Before activating:
- [ ] Test with valid opportunity (all fields populated)
- [ ] Test with missing fields (verify error email)
- [ ] Test with existing project (verify update path)
- [ ] Verify project key generation (unique and correct format)
- [ ] Verify orchestration kicks off
- [ ] Check Chatter/Slack notification appears
- [ ] Verify fault path (intentionally cause error)
---
## Required Salesforce Objects & Fields
### Opportunity Custom Fields
Add these to the Opportunity object if they don't exist:
| Field API Name | Type | Picklist Values | Description |
|----------------|------|-----------------|-------------|
| `Primary_Domain__c` | Text(80) | N/A | Client's desired subdomain |
| `Package_Type__c` | Picklist | OS, Console, Custom | Product package purchased |
| `Service_Tier__c` | Picklist | Starter, Pro, Enterprise | Service level |
| `Project__c` | Lookup(Project__c) | N/A | Link to created project |
---
### Project Custom Object
Create this custom object:
**API Name:** `Project__c`
**Label:** Project
**Plural Label:** Projects
**Record Name:** Project Name (Text)
#### Fields:
| Field API Name | Type | Length/Format | Unique | Required | Description |
|----------------|------|---------------|--------|----------|-------------|
| `Project_Key__c` | Text | 20 | ✅ Yes | ✅ Yes | Unique project identifier (e.g., ACME-1042) |
| `Account__c` | Lookup(Account) | N/A | ❌ | ✅ | Related account |
| `Opportunity__c` | Lookup(Opportunity) | N/A | ❌ | ✅ | Related opportunity |
| `Primary_Domain__c` | Text | 80 | ❌ | ✅ | Client's subdomain |
| `Package_Type__c` | Picklist | N/A | ❌ | ✅ | OS, Console, Custom |
| `Service_Tier__c` | Picklist | N/A | ❌ | ✅ | Starter, Pro, Enterprise |
| `Start_Date__c` | Date | N/A | ❌ | ✅ | Project start date |
| `Status__c` | Picklist | N/A | ❌ | ✅ | Setup In Progress, Active, On Hold, Completed |
| `Technical_Owner__c` | Lookup(User) | N/A | ❌ | ❌ | Lead developer |
| `Backend_Repo_URL__c` | URL | 255 | ❌ | ❌ | GitHub backend repo |
| `Frontend_Repo_URL__c` | URL | 255 | ❌ | ❌ | GitHub frontend repo |
| `Ops_Repo_URL__c` | URL | 255 | ❌ | ❌ | GitHub ops repo |
| `Asana_Project_URL__c` | URL | 255 | ❌ | ❌ | Asana project |
| `Last_Deploy_At__c` | DateTime | N/A | ❌ | ❌ | Last deployment timestamp |
| `Last_Deploy_SHA__c` | Text | 40 | ❌ | ❌ | Git commit SHA |
| `Last_Deploy_Branch__c` | Text | 100 | ❌ | ❌ | Git branch |
| `Last_Deploy_Actor__c` | Text | 100 | ❌ | ❌ | Who deployed |
| `Deploy_Status__c` | Picklist | N/A | ❌ | ❌ | Success, Failed, Rollback |
| `Environment__c` | Picklist | N/A | ❌ | ❌ | Staging, Production |
| `Backend_URL__c` | URL | 255 | ❌ | ❌ | Production backend URL |
| `Frontend_URL__c` | URL | 255 | ❌ | ❌ | Production frontend URL |
| `Release_Notes_URL__c` | URL | 255 | ❌ | ❌ | Link to latest release notes |
#### Page Layouts:
Create page layout with sections:
1. **Project Information** (Name, Project Key, Status, Dates)
2. **Client Details** (Account, Opportunity, Primary Domain, Package, Tier)
3. **Technical Details** (Technical Owner, Repo URLs, Asana URL)
4. **Deployment Info** (Last Deploy fields, URLs)
---
## Deployment Instructions
### Step 1: Create Custom Objects & Fields
Use Salesforce Setup → Object Manager to create:
- Project__c object with all fields listed above
- Custom fields on Opportunity object
### Step 2: Create Named Credentials (for later integrations)
Setup → Named Credentials:
- `GitHub_API` (for calling GitHub API)
- `Asana_API` (for calling Asana API)
- `Slack_Webhook` (for Slack notifications)
### Step 3: Build the Flow
1. Setup → Flows → New Flow
2. Choose "Record-Triggered Flow"
3. Configure trigger as specified above
4. Build each element in order
5. Save as "Opp_Automation_OnStageChange"
6. Activate
### Step 4: Test in Sandbox
1. Create a test opportunity
2. Fill all required fields
3. Change stage to "Closed Won"
4. Verify:
- Project record created
- Fields populated correctly
- Notification sent
- No errors in debug logs
### Step 5: Deploy to Production
Use Change Sets or Salesforce CLI:
```bash
sf project deploy start \
--source-dir force-app/main/default/flows/Opp_Automation_OnStageChange.flow-meta.xml \
--target-org production
```
---
## Monitoring & Maintenance
### Weekly:
- Review flow error logs
- Check for opportunities stuck in "Closed Won" without projects
### Monthly:
- Review project key uniqueness
- Audit project creation success rate (target: >98%)
### Quarterly:
- Review and update picklist values
- Optimize flow performance if needed
---
## Related Documentation
- [Salesforce Orchestration: New Client Kickoff](../orchestrations/new-client-kickoff-orchestration.md)
- [Workflow: New Client Kickoff](../../workflows/new-client-kickoff.md)
- [Integration: Salesforce → GitHub](../../integrations/salesforce-to-github.md)
- [Integration: Salesforce → Asana](../../integrations/salesforce-to-asana.md)
---
## Changelog
| Date | Version | Change | Author |
|------|---------|--------|--------|
| 2025-11-17 | 1.0 | Initial specification | Cece (Claude) |

View File

@@ -0,0 +1,642 @@
# Salesforce Orchestration: New Client Kickoff
**API Name:** `New_Client_Kickoff_Orchestration`
**Type:** Multi-Stage Orchestration
**Status:** Active
**Last Updated:** 2025-11-17
---
## Purpose
This orchestration coordinates the entire **end-to-end new client onboarding process** across multiple teams and systems, from deal closure to go-live.
**Key Benefits:**
- Orchestrates cross-functional work (Sales, Engineering, Customer Success)
- Enforces process consistency
- Provides visibility into project status
- Automates hand-offs between teams
- Ensures nothing falls through the cracks
---
## Input Variables
| Variable Name | Type | Required | Description |
|---------------|------|----------|-------------|
| `ProjectRecordId` | Record ID | ✅ | ID of the Project__c record |
| `OpportunityRecordId` | Record ID | ✅ | ID of the Opportunity |
| `AccountRecordId` | Record ID | ✅ | ID of the Account |
---
## Stages Overview
```
1. Sales Handoff (1-2 days)
2. Technical Setup (3-5 days) ← KEY AUTOMATION STAGE
3. Customer Onboarding (5-10 days)
4. Review & Go-Live (2-3 days)
```
**Total Timeline:** 11-20 days (varies by package complexity)
---
## Stage 1: Sales Handoff
**Owner:** Sales Operations
**Duration:** 1-2 days
**Status Field:** `Project__c.Status__c` = "Sales Handoff"
### Steps
#### Step 1.1: Send Welcome Email to Client
**Element Type:** Action - Send Email
**Template:** `Client_Welcome_Email`
**To:** Primary Contact from Opportunity
**CC:** Account Executive, Account Owner
**From:** ops@blackroad.com
**Merge Fields:**
- `{!Account.Name}`
- `{!Project.Project_Key__c}`
- `{!Project.Package_Type__c}`
- `{!Opportunity.Owner.Name}`
---
#### Step 1.2: Create Internal Kickoff Task
**Element Type:** Action - Create Task
**Assigned To:** Sales Operations Manager
**Subject:** `Internal Kickoff: {!Project.Name}`
**Due Date:** `TODAY() + 1`
**Priority:** High
**Description:**
```
New project needs kickoff coordination:
Project: {!Project.Name}
Project Key: {!Project.Project_Key__c}
Account: {!Account.Name}
Package: {!Project.Package_Type__c}
Service Tier: {!Project.Service_Tier__c}
Action Required:
- Verify all Opportunity fields are complete
- Confirm primary contact is correct
- Schedule kickoff call with client
- Brief technical team on any special requirements
Salesforce Project: {!Project.Id}
```
---
#### Step 1.3: Wait for Approval
**Element Type:** Interactive Step - Screen Flow
**Screen:** "Sales Handoff Complete?"
**Fields:**
- Checkbox: "Kickoff call scheduled"
- Checkbox: "Client expectations set"
- Checkbox: "Special requirements documented"
- Text Area: "Notes for technical team"
**Assigned To:** Sales Operations Manager
**Completion Criteria:** All checkboxes checked
---
#### Step 1.4: Update Project Status
**Element Type:** Action - Update Records
**Record:** Project__c
**Fields:**
- `Status__c` = "Technical Setup"
- `Sales_Handoff_Completed__c` = `TODAY()`
- `Technical_Notes__c` = `{!Screen.Notes}`
---
## Stage 2: Technical Setup
**Owner:** DevOps / Engineering
**Duration:** 3-5 days (mostly automated)
**Status Field:** `Project__c.Status__c` = "Technical Setup"
### Steps
#### Step 2.1: Create GitHub Repositories
**Element Type:** Autolaunched Flow - HTTP Callout
**Named Credential:** `GitHub_API`
**Endpoint:** `POST https://api.github.com/orgs/blackboxprogramming/repos`
**For Each Repo Type:** (Backend, Frontend, Ops)
**Payload:**
```json
{
"name": "blackroad-{!Project.Project_Key__c}-{!RepoType}",
"description": "{!Account.Name} - {!Project.Package_Type__c}",
"private": true,
"auto_init": true,
"gitignore_template": "Python"
}
```
**Store Response:** Capture repo URLs
**Update Project Record:**
- `Backend_Repo_URL__c` = Response.html_url (for backend)
- `Frontend_Repo_URL__c` = Response.html_url (for frontend)
- `Ops_Repo_URL__c` = Response.html_url (for ops)
**See:** [Integration: Salesforce → GitHub](../../integrations/salesforce-to-github.md) for detailed API specs
---
#### Step 2.2: Apply Repository Templates
**Element Type:** Autolaunched Flow - HTTP Callout (Multiple)
**For Each Repo:**
1. **Create Labels**
- POST to `/repos/{owner}/{repo}/labels`
- Apply standard label set from `labels.json`
2. **Apply Branch Protection**
- PUT to `/repos/{owner}/{repo}/branches/main/protection`
- Configure as per branch-protection spec
3. **Create Workflows**
- POST to create files via GitHub API
- Add `ci.yml`, `deploy.yml`, `safety.yml`
4. **Add Secrets**
- POST to `/repos/{owner}/{repo}/actions/secrets`
- Add: `PROJECT_KEY`, `SALESFORCE_INSTANCE_URL`, `SALESFORCE_ACCESS_TOKEN`
---
#### Step 2.3: Create Asana Project
**Element Type:** Autolaunched Flow - HTTP Callout
**Named Credential:** `Asana_API`
**Endpoint:** `POST https://app.asana.com/api/1.0/projects`
**Payload:**
```json
{
"data": {
"name": "{!Account.Name} - {!Project.Project_Key__c}",
"workspace": "{!$Credential.Asana_API.Workspace_GID}",
"team": "{!$Credential.Asana_API.Team_GID}",
"notes": "Salesforce Project: {!Project.Id}\nRepos:\n- Backend: {!Project.Backend_Repo_URL__c}\n- Frontend: {!Project.Frontend_Repo_URL__c}\n- Ops: {!Project.Ops_Repo_URL__c}",
"default_view": "board",
"color": "light-green"
}
}
```
**Store Response:** `{!AsanaProjectGID}`
**Update Project:**
- `Asana_Project_URL__c` = `https://app.asana.com/0/{!AsanaProjectGID}/list`
**See:** [Integration: Salesforce → Asana](../../integrations/salesforce-to-asana.md) for detailed API specs
---
#### Step 2.4: Create Asana Sections & Tasks
**Element Type:** Loop + HTTP Callouts
**For Each Section:** (Discovery, Architecture, Build, Testing, Go-Live)
1. **Create Section:**
```
POST /projects/{!AsanaProjectGID}/sections
{"data": {"name": "Discovery"}}
```
2. **Create Tasks in Section:**
**Discovery Section:**
- Task: "Confirm domain + DNS with client" → Assign to Sales Ops
- Task: "Gather branding assets" → Assign to Design team
**Architecture Section:**
- Task: "Wire up Railway/Cloudflare environments" → Assign to DevOps
- Task: "Enable CI/CD secrets" → Assign to DevOps
- Task: "Design database schema" → Assign to Backend team
**Build Section:**
- Task: "Set up database and migrations" → Assign to Backend
- Task: "Implement authentication" → Assign to Backend
- Task: "Build core UI components" → Assign to Frontend
**Testing Section:**
- Task: "Run end-to-end test suite" → Assign to QA
- Task: "Security scan and review" → Assign to Security team
**Go-Live Section:**
- Task: "Final client walkthrough" → Assign to Customer Success
- Task: "Deploy to production" → Assign to DevOps
**Each Task Payload:**
```json
{
"data": {
"name": "{!TaskName}",
"projects": ["{!AsanaProjectGID}"],
"memberships": [{"project": "{!AsanaProjectGID}", "section": "{!SectionGID}"}],
"assignee": "{!AssigneeEmail}",
"due_on": "{!CalculatedDueDate}",
"notes": "Salesforce Project: {!Project.Id}\nGitHub Repos: {!Project.Backend_Repo_URL__c}"
}
}
```
---
#### Step 2.5: Wait for Repositories to Be Verified
**Element Type:** Interactive Step - Approval
**Approver:** DevOps Manager
**Approval Criteria:**
- [ ] All 3 repos created and accessible
- [ ] CI/CD workflows active
- [ ] Branch protection enabled
- [ ] Secrets configured
**Timeout:** 2 days
**Escalation:** If not approved in 2 days, send alert to Engineering Manager
---
#### Step 2.6: Update Project Status
**Element Type:** Action - Update Records
**Record:** Project__c
**Fields:**
- `Status__c` = "Customer Onboarding"
- `Technical_Setup_Completed__c` = `TODAY()`
---
## Stage 3: Customer Onboarding
**Owner:** Customer Success
**Duration:** 5-10 days
**Status Field:** `Project__c.Status__c` = "Customer Onboarding"
### Steps
#### Step 3.1: Schedule Kickoff Call
**Element Type:** Action - Create Event
**Assigned To:** Customer Success Manager
**Invitees:**
- Primary Contact (from Opportunity)
- Technical Owner (from Project)
- Account Executive
**Subject:** `Kickoff Call: {!Project.Name}`
**Duration:** 1 hour
**Due Date:** `TODAY() + 3`
**Description:**
```
Agenda:
1. Introductions (5 min)
2. Project overview and timeline (10 min)
3. Requirements review (20 min)
4. Q&A (20 min)
5. Next steps (5 min)
Preparation:
- Review Opportunity notes
- Review Asana project board
- Prepare questions about domain, branding, integrations
Links:
- Salesforce Project: {!Project.Id}
- Asana Board: {!Project.Asana_Project_URL__c}
- GitHub Repos: {!Project.Backend_Repo_URL__c}
```
---
#### Step 3.2: Wait for Development Milestones
**Element Type:** Wait - Condition-Based
**Condition:**
- At least 50% of Asana tasks in "Build" section marked complete
**OR**
- Project status manually changed
**OR**
- 10 days elapsed
**Check Frequency:** Daily
---
#### Step 3.3: Send Progress Update to Client
**Element Type:** Action - Send Email (every week)
**Template:** `Client_Progress_Update`
**To:** Primary Contact
**CC:** Account Executive, Technical Owner
**Merge Fields:**
- Progress summary from Asana
- Link to staging environment (if available)
- Next milestones
---
#### Step 3.4: Wait for Testing Complete
**Element Type:** Interactive Step - Checkbox
**Screen:** "Testing Complete?"
**Assigned To:** QA Lead
**Fields:**
- [ ] All end-to-end tests pass
- [ ] Security scan clean
- [ ] Performance acceptable
- [ ] Client UAT completed
---
#### Step 3.5: Update Project Status
**Element Type:** Action - Update Records
**Record:** Project__c
**Fields:**
- `Status__c` = "Review & Go-Live"
- `Onboarding_Completed__c` = `TODAY()`
---
## Stage 4: Review & Go-Live
**Owner:** Customer Success + DevOps
**Duration:** 2-3 days
**Status Field:** `Project__c.Status__c` = "Review & Go-Live"
### Steps
#### Step 4.1: Final Client Walkthrough
**Element Type:** Action - Create Event
**Assigned To:** Customer Success Manager
**Invitees:** Primary Contact, Technical Owner
**Subject:** `Final Review: {!Project.Name}`
**Duration:** 30 minutes
**Due Date:** `TODAY() + 1`
---
#### Step 4.2: Wait for Client Approval
**Element Type:** Interactive Step - Screen Flow
**Screen:** "Client Approval"
**Assigned To:** Customer Success Manager
**Fields:**
- Radio: "Client approval status" (Approved / Needs Changes / Rejected)
- Text Area: "Client feedback"
- Checkbox: "Client trained on system"
**Conditional Logic:**
- If "Needs Changes" → Loop back to Stage 3, Step 3.2
- If "Rejected" → Escalate to Sales leadership
- If "Approved" → Continue
---
#### Step 4.3: Production Deployment
**Element Type:** Action - Create Task
**Assigned To:** DevOps team
**Subject:** `Deploy to Production: {!Project.Project_Key__c}`
**Priority:** High
**Due Date:** `TODAY() + 1`
**Description:**
```
Project is client-approved and ready for production deployment.
Action:
1. Verify staging is stable
2. Create production Railway/Cloudflare environments
3. Merge to main and deploy (will trigger GitHub Actions)
4. Verify health checks
5. Mark this task complete
Links:
- Asana Deploy Task: {!Project.Asana_Project_URL__c}
- Backend Repo: {!Project.Backend_Repo_URL__c}
- Ops Repo: {!Project.Ops_Repo_URL__c}
Note: Deployment will auto-update Salesforce Project record when complete.
```
---
#### Step 4.4: Wait for Deploy Task Complete
**Element Type:** Wait - Task Completion
**Wait For:** Task created in Step 4.3 to be marked complete
**Timeout:** 3 days
**Escalation:** Alert Engineering Manager if not completed
---
#### Step 4.5: Send Go-Live Notification
**Element Type:** Action - Send Email
**Template:** `Client_GoLive_Notification`
**To:** Primary Contact
**CC:** Account Executive, Customer Success Manager
**Merge Fields:**
- Production URL: `{!Project.Frontend_URL__c}`
- Support contact info
- Documentation links
---
#### Step 4.6: Update Project Status to Active
**Element Type:** Action - Update Records
**Record:** Project__c
**Fields:**
- `Status__c` = "Active"
- `Go_Live_Date__c` = `TODAY()`
---
#### Step 4.7: Create Success Metrics Dashboard
**Element Type:** Action - Create Dashboard (optional)
**Purpose:** Track ongoing project health
**Metrics:**
- Deploy frequency
- Error rates
- Customer support tickets
- Feature adoption
---
## Orchestration Complete
**Final Actions:**
1. **Post to Chatter:** Announce successful onboarding
2. **Update Opportunity:** Set `Stage` = "Closed Won - Active"
3. **Create Renewal Opportunity:** Schedule for renewal date
4. **Archive Orchestration:** Store for audit trail
---
## Error Handling
### If GitHub API Fails (Step 2.1-2.2)
1. Log error to Salesforce
2. Create high-priority task for DevOps
3. Send alert to #ops Slack
4. Pause orchestration (manual resume after fix)
### If Asana API Fails (Step 2.3-2.4)
1. Log error
2. Continue orchestration (Asana is nice-to-have)
3. Create manual Asana project (via SOP)
### If Client Approval Rejected (Step 4.2)
1. Loop back to appropriate stage based on feedback
2. Create issue in GitHub with client feedback
3. Notify engineering team
---
## Monitoring & Metrics
### Track These KPIs:
| Metric | Target | Measured By |
|--------|--------|-------------|
| Time to First Commit | < 3 days from Closed Won | GitHub first commit timestamp |
| Time to Go-Live | < 20 days from Closed Won | `Go_Live_Date__c` - `Start_Date__c` |
| Orchestration Completion Rate | > 95% | Successful completions / total starts |
| Stage Escalations | < 5% | Escalations triggered / total projects |
| Client Approval First-Time Rate | > 80% | Approved without loops / total |
### Weekly Review:
- How many orchestrations in flight?
- Any stuck in a stage for > expected duration?
- Any escalations or errors?
### Monthly Audit:
- Review all completed orchestrations
- Identify bottlenecks
- Update stage durations based on actuals
---
## Deployment Instructions
### Prerequisites:
1. Create all custom objects and fields (see Flow spec)
2. Set up Named Credentials (GitHub, Asana, Slack)
3. Create email templates
4. Set up approval processes
### Build Orchestration:
1. **Setup → Orchestrator → New Orchestration**
2. Name: `New_Client_Kickoff_Orchestration`
3. Add stages (1-4) as specified
4. For each stage, add steps as elements
5. Configure inputs/outputs
6. Set wait conditions
7. Add error handlers
8. Activate
### Test in Sandbox:
1. Create test opportunity
2. Move to Closed Won
3. Verify orchestration starts
4. Walk through each stage
5. Verify APIs called correctly
6. Check error handling
### Deploy to Production:
```bash
sf project deploy start \
--source-dir force-app/main/default/orchestrations/New_Client_Kickoff_Orchestration.orchestration-meta.xml \
--target-org production
```
---
## Related Documentation
- [Salesforce Flow: Opp_Automation_OnStageChange](../flows/opp-automation-onstagechange.md)
- [Workflow: New Client Kickoff](../../workflows/new-client-kickoff.md)
- [Integration: Salesforce → GitHub](../../integrations/salesforce-to-github.md)
- [Integration: Salesforce → Asana](../../integrations/salesforce-to-asana.md)
- [Playbook: Brenda's New Client Checklist](../../playbooks/brenda-new-client-checklist.md)
---
## Changelog
| Date | Version | Change | Author |
|------|---------|--------|--------|
| 2025-11-17 | 1.0 | Initial specification | Cece (Claude) |

View File

@@ -0,0 +1,189 @@
name: CI Pipeline
on:
push:
branches: ["**"]
pull_request:
branches: ["main"]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
test:
name: Test (Python ${{ matrix.python-version }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.11", "3.12"]
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: 'pip'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install pytest pytest-cov pytest-asyncio pytest-mock
- name: Run tests with coverage
run: |
pytest --cov=. --cov-report=xml --cov-report=term-missing --cov-report=html -v
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
if: matrix.python-version == '3.12'
with:
file: ./coverage.xml
flags: unittests
name: codecov-umbrella
fail_ci_if_error: false
- name: Archive coverage report
uses: actions/upload-artifact@v4
if: matrix.python-version == '3.12'
with:
name: coverage-report
path: htmlcov/
retention-days: 7
lint:
name: Lint & Format Check
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: 'pip'
- name: Install linting tools
run: |
pip install ruff black isort mypy
- name: Run ruff (fast Python linter)
run: |
ruff check . --output-format=github
- name: Check code formatting with black
run: |
black --check --diff .
- name: Check import sorting with isort
run: |
isort --check-only --diff .
- name: Type check with mypy
run: |
mypy . --ignore-missing-imports --show-error-codes
continue-on-error: true # Don't fail CI on type errors initially
security:
name: Security Scan
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install safety & bandit
run: |
pip install safety bandit[toml]
- name: Check for known vulnerabilities in dependencies
run: |
pip install -r requirements.txt
safety check --json || true
- name: Run bandit security linter
run: |
bandit -r . -f json -o bandit-report.json || true
bandit -r . -f screen
- name: Upload security reports
uses: actions/upload-artifact@v4
with:
name: security-reports
path: |
bandit-report.json
retention-days: 30
build:
name: Build & Test Container
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build Docker image
uses: docker/build-push-action@v5
with:
context: .
push: false
tags: test-build:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
load: true
- name: Test container starts and responds
run: |
docker run -d --name test-container -p 8000:8000 test-build:${{ github.sha }}
sleep 10
# Check if container is still running
if [ "$(docker inspect -f '{{.State.Running}}' test-container)" != "true" ]; then
echo "Container failed to start"
docker logs test-container
exit 1
fi
# Check health endpoint
curl -f http://localhost:8000/health || exit 1
docker stop test-container
docker rm test-container
summary:
name: CI Summary
runs-on: ubuntu-latest
needs: [test, lint, security, build]
if: always()
steps:
- name: Check all jobs status
run: |
echo "Test: ${{ needs.test.result }}"
echo "Lint: ${{ needs.lint.result }}"
echo "Security: ${{ needs.security.result }}"
echo "Build: ${{ needs.build.result }}"
if [ "${{ needs.test.result }}" != "success" ] || \
[ "${{ needs.lint.result }}" != "success" ] || \
[ "${{ needs.build.result }}" != "success" ]; then
echo "❌ CI pipeline failed"
exit 1
fi
echo "✅ All CI checks passed"

View File

@@ -0,0 +1,303 @@
name: Deploy to Production
on:
push:
branches:
- main
release:
types: [published]
workflow_dispatch:
inputs:
environment:
description: 'Environment to deploy to'
required: true
default: 'production'
type: choice
options:
- staging
- production
env:
RAILWAY_SERVICE: backend
ENVIRONMENT: ${{ github.event.inputs.environment || 'production' }}
jobs:
deploy-backend:
name: Deploy Backend to Railway
runs-on: ubuntu-latest
environment: ${{ github.event.inputs.environment || 'production' }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install Railway CLI
run: |
curl -fsSL https://railway.app/install.sh | sh
echo "$HOME/.railway/bin" >> $GITHUB_PATH
- name: Deploy to Railway
env:
RAILWAY_TOKEN: ${{ secrets.RAILWAY_TOKEN }}
run: |
railway up --service ${{ env.RAILWAY_SERVICE }} --environment ${{ env.ENVIRONMENT }}
- name: Wait for deployment to complete
env:
RAILWAY_TOKEN: ${{ secrets.RAILWAY_TOKEN }}
run: |
echo "Waiting for Railway deployment to stabilize..."
sleep 30
- name: Get deployment URL
id: get-url
env:
RAILWAY_TOKEN: ${{ secrets.RAILWAY_TOKEN }}
run: |
DEPLOY_URL=$(railway domain --service ${{ env.RAILWAY_SERVICE }} --environment ${{ env.ENVIRONMENT }} 2>/dev/null || echo "")
echo "DEPLOY_URL=$DEPLOY_URL" >> $GITHUB_OUTPUT
echo "Backend deployed to: $DEPLOY_URL"
- name: Health check
if: steps.get-url.outputs.DEPLOY_URL != ''
run: |
echo "Running health check on ${{ steps.get-url.outputs.DEPLOY_URL }}"
# Retry health check up to 10 times
for i in {1..10}; do
if curl -f "${{ steps.get-url.outputs.DEPLOY_URL }}/health" 2>/dev/null; then
echo "✅ Health check passed"
exit 0
fi
echo "Health check attempt $i failed, retrying in 10s..."
sleep 10
done
echo "❌ Health check failed after 10 attempts"
exit 1
- name: Save deployment metadata
id: metadata
run: |
echo "DEPLOY_TIME=$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> $GITHUB_OUTPUT
echo "DEPLOY_SHA=${{ github.sha }}" >> $GITHUB_OUTPUT
echo "DEPLOY_ACTOR=${{ github.actor }}" >> $GITHUB_OUTPUT
echo "DEPLOY_BRANCH=${{ github.ref_name }}" >> $GITHUB_OUTPUT
outputs:
deploy_url: ${{ steps.get-url.outputs.DEPLOY_URL }}
deploy_time: ${{ steps.metadata.outputs.DEPLOY_TIME }}
deploy_sha: ${{ steps.metadata.outputs.DEPLOY_SHA }}
deploy-frontend:
name: Deploy Frontend to Cloudflare Pages
runs-on: ubuntu-latest
environment: ${{ github.event.inputs.environment || 'production' }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build frontend
env:
VITE_API_URL: ${{ secrets.VITE_API_URL }}
VITE_ENV: ${{ env.ENVIRONMENT }}
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: ${{ secrets.CLOUDFLARE_PROJECT_NAME }}
directory: dist
gitHubToken: ${{ secrets.GITHUB_TOKEN }}
branch: ${{ github.ref_name }}
- name: Verify frontend deployment
run: |
FRONTEND_URL="${{ secrets.CLOUDFLARE_PAGES_URL }}"
echo "Verifying frontend at $FRONTEND_URL"
# Wait for Cloudflare propagation
sleep 30
# Check if frontend loads
for i in {1..5}; do
if curl -f "$FRONTEND_URL" 2>/dev/null; then
echo "✅ Frontend is accessible"
exit 0
fi
echo "Attempt $i failed, retrying in 10s..."
sleep 10
done
echo "❌ Frontend verification failed"
exit 1
notify-stakeholders:
name: Notify Stakeholders
needs: [deploy-backend, deploy-frontend]
runs-on: ubuntu-latest
if: success()
steps:
- name: Extract Project Key from repo name
id: project-key
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')
if [ -z "$PROJECT_KEY" ]; then
echo "Warning: Could not extract project key from repo name"
PROJECT_KEY="UNKNOWN"
fi
echo "PROJECT_KEY=$PROJECT_KEY" >> $GITHUB_OUTPUT
echo "Extracted project key: $PROJECT_KEY"
- name: Update Salesforce Project record
if: steps.project-key.outputs.PROJECT_KEY != 'UNKNOWN'
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/${{ steps.project-key.outputs.PROJECT_KEY }}" \
-H "Authorization: Bearer $SF_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"Last_Deploy_At__c": "${{ needs.deploy-backend.outputs.deploy_time }}",
"Last_Deploy_SHA__c": "${{ needs.deploy-backend.outputs.deploy_sha }}",
"Last_Deploy_Branch__c": "${{ github.ref_name }}",
"Last_Deploy_Actor__c": "${{ github.actor }}",
"Deploy_Status__c": "Success",
"Environment__c": "${{ env.ENVIRONMENT }}",
"Backend_URL__c": "${{ needs.deploy-backend.outputs.deploy_url }}",
"Release_Notes_URL__c": "https://github.com/${{ github.repository }}/commit/${{ github.sha }}"
}' || echo "Warning: Failed to update Salesforce"
- name: Create Salesforce Deployment record
if: steps.project-key.outputs.PROJECT_KEY != 'UNKNOWN'
env:
SF_INSTANCE_URL: ${{ secrets.SALESFORCE_INSTANCE_URL }}
SF_ACCESS_TOKEN: ${{ secrets.SALESFORCE_ACCESS_TOKEN }}
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 '{
"Name": "${{ steps.project-key.outputs.PROJECT_KEY }} - ${{ github.sha }}",
"Project_Key__c": "${{ steps.project-key.outputs.PROJECT_KEY }}",
"Deployed_At__c": "${{ needs.deploy-backend.outputs.deploy_time }}",
"Git_SHA__c": "${{ github.sha }}",
"Git_Branch__c": "${{ github.ref_name }}",
"Deployed_By__c": "${{ github.actor }}",
"Status__c": "Success",
"Environment__c": "${{ env.ENVIRONMENT }}",
"Repository__c": "${{ github.repository }}",
"Commit_URL__c": "https://github.com/${{ github.repository }}/commit/${{ github.sha }}"
}' || echo "Warning: Failed to create Deployment record"
- name: Update Asana tasks
if: steps.project-key.outputs.PROJECT_KEY != 'UNKNOWN' && env.ENVIRONMENT == 'production'
env:
ASANA_PAT: ${{ secrets.ASANA_PAT }}
ASANA_WORKSPACE_GID: ${{ secrets.ASANA_WORKSPACE_GID }}
run: |
PROJECT_KEY="${{ steps.project-key.outputs.PROJECT_KEY }}"
# Find Asana project by name containing PROJECT_KEY
PROJECT_GID=$(curl -s "https://app.asana.com/api/1.0/projects?workspace=$ASANA_WORKSPACE_GID&opt_fields=name,gid" \
-H "Authorization: Bearer $ASANA_PAT" | \
jq -r ".data[] | select(.name | contains(\"$PROJECT_KEY\")) | .gid" | head -n 1)
if [ -z "$PROJECT_GID" ]; then
echo "Warning: Could not find Asana project for $PROJECT_KEY"
exit 0
fi
echo "Found Asana project: $PROJECT_GID"
# Find "Deploy" task
TASK_GID=$(curl -s "https://app.asana.com/api/1.0/projects/$PROJECT_GID/tasks?opt_fields=name,gid,completed" \
-H "Authorization: Bearer $ASANA_PAT" | \
jq -r '.data[] | select(.name | test("Deploy.*production"; "i")) | select(.completed == false) | .gid' | head -n 1)
if [ -z "$TASK_GID" ]; then
echo "No pending deploy task found in Asana"
exit 0
fi
echo "Found deploy task: $TASK_GID"
# Mark task 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}}' || echo "Warning: Failed to complete task"
# 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:** ${{ needs.deploy-backend.outputs.deploy_time }}\n**Link:** https://github.com/${{ github.repository }}/commit/${{ github.sha }}"
}
}' || echo "Warning: Failed to add comment"
- name: Notify Slack
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_DEPLOYS }}
run: |
STATUS_EMOJI="✅"
curl -X POST "$SLACK_WEBHOOK_URL" \
-H "Content-Type: application/json" \
-d '{
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "'"$STATUS_EMOJI"' *Deploy to '"${{ env.ENVIRONMENT }}"'*\n\n*Project:* '"${{ steps.project-key.outputs.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*Backend:* ${{ needs.deploy-backend.outputs.deploy_url }}"
}
}
]
}' || echo "Warning: Failed to send Slack notification"
rollback-on-failure:
name: Rollback on Failure
needs: [deploy-backend, deploy-frontend]
runs-on: ubuntu-latest
if: failure()
steps:
- name: Notify failure
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_DEPLOYS }}
run: |
curl -X POST "$SLACK_WEBHOOK_URL" \
-H "Content-Type: application/json" \
-d '{
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "❌ *Deploy FAILED*\n\n*Repo:* `${{ github.repository }}`\n*Commit:* <https://github.com/${{ github.repository }}/commit/${{ github.sha }}|'"${GITHUB_SHA:0:7}"'>\n*By:* ${{ github.actor }}\n\n⚠ Manual intervention may be required. Check the <https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}|workflow run> for details."
}
}
]
}' || true

View File

@@ -0,0 +1,238 @@
name: Security & Safety Checks
on:
push:
branches: ["main"]
pull_request:
branches: ["main"]
schedule:
# Run weekly on Monday at 9am UTC
- cron: '0 9 * * 1'
workflow_dispatch:
permissions:
contents: read
security-events: write
actions: read
jobs:
dependency-scan:
name: Dependency Vulnerability Scan
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install safety pip-audit
- name: Run safety check
continue-on-error: true
run: |
safety check --json --output safety-report.json || true
safety check
- name: Run pip-audit
continue-on-error: true
run: |
pip-audit --format json --output pip-audit-report.json || true
pip-audit
- name: Upload vulnerability reports
uses: actions/upload-artifact@v4
with:
name: dependency-vulnerability-reports
path: |
safety-report.json
pip-audit-report.json
retention-days: 90
sast-scan:
name: Static Application Security Testing
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install bandit
run: pip install bandit[toml]
- name: Run bandit security linter
run: |
bandit -r . \
-f json \
-o bandit-report.json \
--exclude './tests/*,./venv/*,./.venv/*,*/site-packages/*' \
-ll || true
bandit -r . \
-f screen \
--exclude './tests/*,./venv/*,./.venv/*,*/site-packages/*' \
-ll
- name: Upload SAST report
uses: actions/upload-artifact@v4
with:
name: sast-report
path: bandit-report.json
retention-days: 90
secret-scan:
name: Secret Detection
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0 # Full history for secret scanning
- name: Run Gitleaks
uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITLEAKS_LICENSE: ${{ secrets.GITLEAKS_LICENSE}}
codeql:
name: CodeQL Analysis
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: python, javascript
queries: security-and-quality
- name: Autobuild
uses: github/codeql-action/autobuild@v3
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
with:
category: "/language:python"
license-check:
name: License Compliance Check
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install pip-licenses
run: pip install pip-licenses
- name: Install project dependencies
run: pip install -r requirements.txt
- name: Check licenses
run: |
echo "Generating license report..."
pip-licenses --format=json --output-file=licenses.json
pip-licenses --format=markdown --output-file=licenses.md
# Check for problematic licenses (GPL, AGPL, etc.)
PROBLEMATIC=$(pip-licenses --format=json | jq -r '.[] | select(.License | test("GPL|AGPL")) | .Name')
if [ -n "$PROBLEMATIC" ]; then
echo "⚠️ Warning: Found packages with potentially problematic licenses:"
echo "$PROBLEMATIC"
# Don't fail the build, just warn
else
echo "✅ No problematic licenses found"
fi
- name: Upload license report
uses: actions/upload-artifact@v4
with:
name: license-report
path: |
licenses.json
licenses.md
retention-days: 90
container-scan:
name: Container Image Scan
runs-on: ubuntu-latest
if: github.event_name != 'schedule'
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Build Docker image
run: docker build -t security-scan:${{ github.sha }} .
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: security-scan:${{ github.sha }}
format: 'sarif'
output: 'trivy-results.sarif'
severity: 'CRITICAL,HIGH'
- name: Upload Trivy results to GitHub Security
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: 'trivy-results.sarif'
- name: Run Trivy for readable output
uses: aquasecurity/trivy-action@master
with:
image-ref: security-scan:${{ github.sha }}
format: 'table'
severity: 'CRITICAL,HIGH,MEDIUM'
summary:
name: Security Summary
needs: [dependency-scan, sast-scan, secret-scan, license-check]
runs-on: ubuntu-latest
if: always()
steps:
- name: Security check summary
run: |
echo "### Security Scan Results"
echo ""
echo "- Dependency Scan: ${{ needs.dependency-scan.result }}"
echo "- SAST Scan: ${{ needs.sast-scan.result }}"
echo "- Secret Scan: ${{ needs.secret-scan.result }}"
echo "- License Check: ${{ needs.license-check.result }}"
echo ""
if [ "${{ needs.secret-scan.result }}" == "failure" ]; then
echo "❌ CRITICAL: Secrets detected in repository!"
exit 1
fi
if [ "${{ needs.dependency-scan.result }}" == "failure" ] || \
[ "${{ needs.sast-scan.result }}" == "failure" ]; then
echo "⚠️ Security vulnerabilities detected. Review the reports."
# Don't fail the build for vulnerability warnings
else
echo "✅ No critical security issues detected"
fi

View File

@@ -0,0 +1,61 @@
---
name: Bug Report
about: Report a bug or unexpected behavior
title: '[BUG] '
labels: 'type:bug, priority:p2'
assignees: ''
---
## Description
<!-- Clear and concise description of the bug -->
## Steps to Reproduce
1.
2.
3.
## Expected Behavior
<!-- What should happen -->
## Actual Behavior
<!-- What actually happens -->
## Environment
- **Environment:** (production / staging / local)
- **Browser/Client:** (if applicable)
- **OS:** (if applicable)
- **Version/Commit:**
## Screenshots/Logs
<!-- If applicable, add screenshots or relevant error logs -->
```
Paste error logs here
```
## Possible Fix
<!-- Optional: Suggest a potential solution or workaround -->
## Additional Context
<!-- Any other relevant information -->
## Impact
- [ ] Blocks production users
- [ ] Blocks internal users
- [ ] Minor inconvenience
- [ ] Nice to have fix
## Related
- **Asana Task:** [link]
- **Salesforce Case:** [link]
- **Related Issues:** #

View File

@@ -0,0 +1,114 @@
---
name: Deployment Checklist
about: Pre/post deployment checklist
title: '[DEPLOY] Release v'
labels: 'type:deployment, priority:p1'
assignees: ''
---
## Release Information
- **Version:** v
- **Target Date:**
- **Environment:** (staging / production)
- **Deployment Window:**
## Pre-Deployment Checklist
### Code & Testing
- [ ] All PRs merged to `main`
- [ ] CI pipeline passes (tests, lint, build)
- [ ] Security scans pass
- [ ] Code review completed
- [ ] Release notes drafted
### Infrastructure
- [ ] Database migrations tested in staging
- [ ] Environment variables updated (if needed)
- [ ] Secrets rotated (if needed)
- [ ] Resource scaling planned (if needed)
### Stakeholder Communication
- [ ] Asana tasks updated with deploy plan
- [ ] Salesforce Project record current
- [ ] #deploys channel notified
- [ ] Customer success team informed (if customer-facing changes)
### Backup & Rollback
- [ ] Database backup created
- [ ] Current version tagged for rollback
- [ ] Rollback procedure documented
## Deployment Steps
1. [ ] Tag release in GitHub
2. [ ] Trigger deploy workflow
3. [ ] Monitor deployment logs
4. [ ] Wait for health checks to pass
5. [ ] Verify backend API responding
6. [ ] Verify frontend loads correctly
## Post-Deployment Checklist
### Verification
- [ ] Health checks passing
- [ ] Critical user flows tested
- [ ] Database migrations applied
- [ ] No errors in application logs
- [ ] No spike in error rates (monitoring)
- [ ] Performance metrics within acceptable range
### Stakeholder Updates
- [ ] Salesforce Project record updated (automated)
- [ ] Asana deploy task marked complete (automated)
- [ ] Slack notification sent (automated)
- [ ] Release notes published
### Monitoring
- [ ] Set up alerts for next 24 hours
- [ ] Monitor error rates
- [ ] Monitor performance metrics
- [ ] Check user feedback channels
## Rollback Plan
**Trigger Rollback If:**
- Critical functionality broken
- Error rate > 5%
- Performance degradation > 50%
- Database corruption detected
**Rollback Procedure:**
1. Navigate to: https://github.com/$REPO/actions/workflows/rollback.yml
2. Click "Run workflow"
3. Enter previous stable SHA:
4. Enter rollback reason:
5. Monitor rollback completion
## Issues Discovered
<!-- Document any issues found during/after deployment -->
| Issue | Severity | Status | Resolution |
|-------|----------|--------|------------|
| | | | |
## Post-Mortem Notes
<!-- After deployment, document lessons learned -->
**What went well:**
-
**What could be improved:**
-
**Action items:**
- [ ]
- [ ]
## Related
- **Release Notes:** [link]
- **Salesforce Project:** [link]
- **Asana Deploy Task:** [link]

View File

@@ -0,0 +1,57 @@
---
name: Feature Request
about: Suggest a new feature or enhancement
title: '[FEATURE] '
labels: 'type:feature, priority:p3'
assignees: ''
---
## Problem/Need
<!-- What problem does this feature solve? Who needs it? -->
## Proposed Solution
<!-- Describe your proposed solution -->
## Alternatives Considered
<!-- What other approaches did you consider? Why is this the best option? -->
## User Stories
<!-- How will users interact with this feature? -->
As a **[user type]**, I want **[goal]** so that **[benefit]**.
## Success Criteria
<!-- How will we know this feature is successful? -->
- [ ] Criterion 1
- [ ] Criterion 2
- [ ] Criterion 3
## Technical Considerations
<!-- Any technical constraints, dependencies, or implementation notes -->
## Mockups/Designs
<!-- Optional: Add mockups, wireframes, or design references -->
## Priority Justification
- **Business Value:** (High / Medium / Low)
- **Technical Complexity:** (High / Medium / Low)
- **User Impact:** (High / Medium / Low)
## Related
- **Asana Epic:** [link]
- **Salesforce Opportunity:** [link]
- **Related Issues:** #
## Additional Context
<!-- Any other relevant information -->

View File

@@ -0,0 +1,206 @@
# Branch Protection Configuration
This document specifies the branch protection rules to be applied to all BlackRoad project repositories.
## Main Branch Protection
**Branch:** `main`
### Settings
**Require Pull Request:**
- ✅ Require a pull request before merging
- Require approvals: **1**
- Dismiss stale pull request approvals when new commits are pushed
- Require review from Code Owners (if CODEOWNERS file exists)
**Status Checks:**
- ✅ Require status checks to pass before merging
- ✅ Require branches to be up to date before merging
**Required Status Checks:**
- `test (3.11)`
- `test (3.12)`
- `lint`
- `build`
- `security / summary` (allow to fail)
**Restrictions:**
- ✅ Restrict who can push to matching branches
- Allowed to push: **Repository admins only**
- Allowed to bypass: **None** (not even admins)
**Other Rules:**
- ✅ Require linear history (enforce rebase or squash merge)
- ✅ Require deployments to succeed before merging (if applicable)
- ✅ Lock branch (prevent all changes) - **❌ Disabled** (allow normal development)
- ✅ Do not allow force pushes
- ✅ Do not allow deletions
**Enforcement:**
- ✅ Include administrators (admins must follow the same rules)
---
## Development Branch Protection (Optional)
**Branch:** `develop` (if using GitFlow)
### Settings
**Require Pull Request:**
- ✅ Require a pull request before merging
- Require approvals: **1**
**Status Checks:**
- ✅ Require status checks to pass before merging
- Required checks: `test`, `lint`, `build`
**Other Rules:**
- ✅ Do not allow force pushes
- ✅ Do not allow deletions
---
## Tag Protection
**Pattern:** `v*` (all version tags)
### Settings
- ✅ Only repository admins can create tags matching this pattern
- ✅ Only repository admins can delete tags matching this pattern
**Purpose:** Prevent accidental or malicious deletion of release tags
---
## Implementation
### Via GitHub API
Use this script to apply branch protection rules programmatically:
```bash
#!/bin/bash
REPO="blackboxprogramming/blackroad-{PROJECT_KEY}-backend"
BRANCH="main"
TOKEN="${GITHUB_TOKEN}"
curl -X PUT \
"https://api.github.com/repos/${REPO}/branches/${BRANCH}/protection" \
-H "Authorization: token ${TOKEN}" \
-H "Accept: application/vnd.github.v3+json" \
-d '{
"required_status_checks": {
"strict": true,
"contexts": [
"test (3.11)",
"test (3.12)",
"lint",
"build"
]
},
"enforce_admins": true,
"required_pull_request_reviews": {
"dismissal_restrictions": {},
"dismiss_stale_reviews": true,
"require_code_owner_reviews": true,
"required_approving_review_count": 1
},
"restrictions": null,
"required_linear_history": true,
"allow_force_pushes": false,
"allow_deletions": false
}'
```
### Via GitHub Web UI
1. Go to repository → Settings → Branches
2. Click "Add branch protection rule"
3. Branch name pattern: `main`
4. Configure settings as specified above
5. Click "Create" or "Save changes"
---
## CODEOWNERS File
Create `.github/CODEOWNERS` to automatically request reviews from specific teams:
```
# Default owners for everything
* @blackboxprogramming/engineering
# Backend code
/backend/ @blackboxprogramming/backend-team
# Frontend code
/frontend/ @blackboxprogramming/frontend-team
# Infrastructure
/ops/ @blackboxprogramming/devops-team
/terraform/ @blackboxprogramming/devops-team
/.github/workflows/ @blackboxprogramming/devops-team
# Documentation
/docs/ @blackboxprogramming/documentation-team
*.md @blackboxprogramming/documentation-team
# Security-sensitive files
/secrets/ @blackboxprogramming/security-team
.env.* @blackboxprogramming/security-team
```
---
## Merge Strategy
**Preferred:** Squash and merge
**Reasoning:**
- Clean, linear history
- Each PR becomes a single commit
- Easy to revert if needed
- Clear attribution
**Alternative:** Rebase and merge (for repos with well-structured commit history)
**Avoid:** Merge commits (creates messy history)
---
## Exceptions
**When to bypass branch protection:**
- **NEVER** for regular development
- Only in absolute emergencies:
- Critical production bug fix (with manager approval)
- Security vulnerability patch (with security team approval)
- Service outage (with on-call engineer approval)
**Process for emergency bypass:**
1. Get approval in #ops or #engineering Slack channel
2. Document reason in channel
3. Make the emergency change
4. Create follow-up PR immediately after to document the change
5. Post-mortem within 24 hours
---
## Monitoring
**Weekly:** Review bypass logs
**Monthly:** Audit branch protection settings across all repos
**Quarterly:** Review and update required status checks
**Tool:** Use GitHub audit log API to track who bypassed protection and why
---
## Related
- [GitHub Actions: CI Workflow](../../github-actions/ci.yml)
- [New Client Kickoff Workflow](../../workflows/new-client-kickoff.md)
- [Pull Request Template](./pull_request_template.md)

View File

@@ -0,0 +1,137 @@
[
{
"name": "type:feature",
"color": "0E8A16",
"description": "New feature or enhancement"
},
{
"name": "type:bug",
"color": "D73A4A",
"description": "Bug or defect"
},
{
"name": "type:docs",
"color": "0075CA",
"description": "Documentation changes"
},
{
"name": "type:refactor",
"color": "FBCA04",
"description": "Code refactoring without functionality change"
},
{
"name": "type:test",
"color": "BFD4F2",
"description": "Test-related changes"
},
{
"name": "type:chore",
"color": "FEF2C0",
"description": "Maintenance, dependencies, tooling"
},
{
"name": "type:security",
"color": "D93F0B",
"description": "Security-related changes"
},
{
"name": "type:deployment",
"color": "5319E7",
"description": "Deployment or release-related"
},
{
"name": "priority:p0",
"color": "B60205",
"description": "Critical - Drop everything"
},
{
"name": "priority:p1",
"color": "D93F0B",
"description": "High - Should be next"
},
{
"name": "priority:p2",
"color": "FBCA04",
"description": "Medium - Normal priority"
},
{
"name": "priority:p3",
"color": "C5DEF5",
"description": "Low - Nice to have"
},
{
"name": "area:backend",
"color": "5319E7",
"description": "Backend/API changes"
},
{
"name": "area:frontend",
"color": "1D76DB",
"description": "Frontend/UI changes"
},
{
"name": "area:ops",
"color": "0E8A16",
"description": "Infrastructure/DevOps"
},
{
"name": "area:infra",
"color": "006B75",
"description": "Infrastructure as code"
},
{
"name": "area:database",
"color": "5319E7",
"description": "Database/schema changes"
},
{
"name": "status:blocked",
"color": "D93F0B",
"description": "Blocked by external dependency"
},
{
"name": "status:in-review",
"color": "FBCA04",
"description": "In code review"
},
{
"name": "status:needs-info",
"color": "D876E3",
"description": "Needs more information"
},
{
"name": "status:wontfix",
"color": "FFFFFF",
"description": "Will not be addressed"
},
{
"name": "release",
"color": "0E8A16",
"description": "Trigger deployment on merge"
},
{
"name": "breaking-change",
"color": "D93F0B",
"description": "Contains breaking changes"
},
{
"name": "good-first-issue",
"color": "7057FF",
"description": "Good for newcomers"
},
{
"name": "help-wanted",
"color": "008672",
"description": "Extra attention needed"
},
{
"name": "automation",
"color": "BFDADC",
"description": "Automation or workflow changes"
},
{
"name": "automation-bug",
"color": "D93F0B",
"description": "Automation system malfunction"
}
]

View File

@@ -0,0 +1,50 @@
## What
<!-- Brief description of what this PR changes -->
## Why
<!-- Business/technical justification for these changes -->
## How
<!-- Implementation approach and key technical decisions -->
## Testing
- [ ] Unit tests added/updated
- [ ] Integration tests pass
- [ ] Manual testing completed
- [ ] Edge cases considered
## Checklist
- [ ] Code follows project style guidelines
- [ ] Self-reviewed the code
- [ ] Commented complex/non-obvious code
- [ ] Documentation updated (if applicable)
- [ ] No new warnings introduced
- [ ] Backward compatibility maintained (or breaking changes documented)
## Screenshots/Logs
<!-- If applicable, add screenshots or relevant logs -->
## Related
- **Asana Task:** [link]
- **Salesforce Project:** [link]
- **Related Issues:** Closes #
## Deployment Notes
<!-- Any special deployment steps, migrations, or configuration changes needed -->
---
**Reviewer Checklist:**
- [ ] Code is clean and maintainable
- [ ] Tests are adequate
- [ ] No security vulnerabilities introduced
- [ ] Performance implications considered
- [ ] Documentation is clear

View File

@@ -0,0 +1,384 @@
# New Client / Project Kickoff Workflow
**Owner:** Operations
**Systems:** Salesforce → GitHub → Asana
**Status:** Active
**Last Updated:** 2025-11-17
---
## Overview
This is the **flagship automation workflow** that orchestrates the entire journey from "deal closed" to "project ready for development." It connects Salesforce, GitHub, and Asana to eliminate manual setup and ensure consistency.
**Flow Duration:** ~5-10 minutes (automated)
**Human Touch Points:** Initial validation + final review
---
## The Golden Path
```
Salesforce Opp (Closed Won)
Salesforce Flow creates Project record
Orchestration kicks off (4 stages)
Stage 2: Technical Setup
├─→ GitHub: Create repos + CI/CD
└─→ Asana: Create project + tasks
Feedback loops: Deploy events → Salesforce + Asana
```
---
## Stage-by-Stage Breakdown
### **Stage A: Salesforce Opportunity Closure**
**Trigger:**
Sales Stage moves to `Closed Won` on Opportunity
**Salesforce Flow:** `Opp_Automation_OnStageChange`
**Actions:**
1. **Validate required fields:**
- Account Name
- Primary Contact
- Domain/Subdomain
- Package Type (OS / Console / Custom)
- Service Tier (Starter / Pro / Enterprise)
2. **Create/Update Records:**
- Link or create Account record
- Link or create Contact record (Primary)
- Create **Project** record (Custom Object: `Project__c`)
3. **Project Record Fields:**
```
Project_Key__c: {ACCOUNT_SLUG}-{RANDOM_4CHAR}
Example: ACME-X7K9
Account__c: Lookup to Account
Opportunity__c: Lookup to Opportunity
Primary_Domain__c: Text (e.g., "acme-portal")
Package_Type__c: Picklist (OS / Console / Custom)
Service_Tier__c: Picklist (Starter / Pro / Enterprise)
Start_Date__c: Date (auto: today)
Status__c: Picklist → "Setup In Progress"
Technical_Owner__c: Lookup to User
```
4. **Launch Orchestration:**
- Name: `New_Client_Kickoff_Orchestration`
- Input: Project record ID
- Stages:
- Sales Handoff
- Technical Setup ← **We care about this**
- Customer Onboarding
- Review & Go-Live
**Outputs:**
✅ Project record created
✅ Orchestration started
✅ Notification sent to #ops Slack channel (optional)
---
### **Stage B: GitHub Repository Scaffolding**
**Trigger:**
Orchestration Stage "Technical Setup" begins
**Mechanism:**
Salesforce **Autolaunched Flow** → HTTP Callout to GitHub API (or GitHub App webhook receiver)
**API Endpoint:**
```
POST https://api.github.com/orgs/blackboxprogramming/repos
Authorization: Bearer {GITHUB_APP_TOKEN}
```
**Payload (per repo):**
```json
{
"name": "blackroad-{project_key}-backend",
"description": "Backend for {Account Name} ({Project Key})",
"private": true,
"auto_init": true,
"gitignore_template": "Python",
"license_template": "mit"
}
```
**Repos Created:**
1. `blackroad-{project_key}-backend`
2. `blackroad-{project_key}-frontend`
3. `blackroad-{project_key}-ops`
**Apply Repo Template** (via GitHub API or Actions):
1. **Branch Protection** (on `main`):
- Require PR before merge
- Require 1 approval
- Require status checks: `test`, `lint`, `build`
- No force pushes
- No deletions
2. **Labels** (via API):
```
type:feature, type:bug, type:docs, type:refactor
priority:p0, priority:p1, priority:p2, priority:p3
area:backend, area:frontend, area:ops, area:infra
status:blocked, status:in-review
```
3. **Issue Templates** (from `sop/templates/repo-template/.github/ISSUE_TEMPLATE/`):
- `bug_report.md`
- `feature_request.md`
- `deployment_checklist.md`
4. **PR Template** (`.github/pull_request_template.md`)
5. **GitHub Actions Workflows** (from `sop/templates/github-actions/`):
- `.github/workflows/ci.yml` Run tests + lint on every push/PR
- `.github/workflows/deploy.yml` Deploy on release tag or approved PR to `main`
- `.github/workflows/safety.yml` SAST + dependency scanning
- `.github/workflows/notify-salesforce.yml` Send deploy events back to Salesforce
**Secrets Setup** (manual or via API):
```
RAILWAY_TOKEN
CLOUDFLARE_API_TOKEN
SALESFORCE_WEBHOOK_URL
ASANA_API_TOKEN
```
**Outputs:**
✅ 3 repos created
✅ CI/CD pipelines active
✅ Branch protection enabled
✅ Repo URLs written back to Salesforce Project record:
- `Backend_Repo_URL__c`
- `Frontend_Repo_URL__c`
- `Ops_Repo_URL__c`
---
### **Stage C: Asana Task Space for Humans**
**Trigger:**
Same Orchestration Stage "Technical Setup", after GitHub repos created
**Mechanism:**
Salesforce Flow → HTTP Callout to Asana API (or via Zapier/Make)
**API Endpoint:**
```
POST https://app.asana.com/api/1.0/projects
Authorization: Bearer {ASANA_PAT}
```
**Payload:**
```json
{
"data": {
"name": "{Account Name} - {Project Key}",
"notes": "Salesforce Project: {Project_URL}\nRepos:\n- Backend: {Backend_Repo_URL}\n- Frontend: {Frontend_Repo_URL}\n- Ops: {Ops_Repo_URL}",
"workspace": "{WORKSPACE_GID}",
"team": "{TEAM_GID}",
"default_view": "board",
"color": "light-green"
}
}
```
**Create Sections:**
```
POST /projects/{project_gid}/sections
{
"data": {
"name": "Discovery"
}
}
```
Repeat for: `Architecture`, `Build`, `Testing`, `Go-Live`
**Create Standard Tasks:**
| Section | Task | Assignee | Description |
|---------|------|----------|-------------|
| Discovery | Confirm domain + DNS with client | Sales Ops | Get final domain, subdomain, and DNS setup requirements |
| Discovery | Gather branding assets | Design | Logo, colors, fonts for custom theming |
| Architecture | Wire up Railway/Cloudflare envs | DevOps | Create staging + production environments |
| Architecture | Enable CI/CD secrets | DevOps | Add RAILWAY_TOKEN, CLOUDFLARE_API_TOKEN to GitHub |
| Build | Set up database schema | Backend | Initialize Postgres + migrations |
| Build | Implement authentication | Backend | SSO or email/password setup |
| Testing | Run first end-to-end test | QA | Verify deploy pipeline works end-to-end |
| Go-Live | Final client walkthrough | Customer Success | Demo + training session |
**Each Task Contains:**
- Link to Salesforce Project record
- Link to relevant GitHub repo + issues
- Due date (calculated from Project start date + offsets)
**Outputs:**
✅ Asana project created
✅ Standard task set populated
✅ Asana project URL written back to Salesforce:
- `Asana_Project_URL__c`
---
### **Stage D: Feedback Loop GitHub → Salesforce & Asana**
**Trigger:**
GitHub events:
- PR merged to `main` with label `release`
- GitHub Release created
- CI pipeline `deploy` job succeeds
**Mechanism:**
GitHub Action: `.github/workflows/notify-salesforce.yml`
**Action Workflow (Pseudocode):**
```yaml
name: Notify Salesforce & Asana on Deploy
on:
workflow_run:
workflows: ["Deploy"]
types:
- completed
jobs:
notify:
runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion == 'success' }}
steps:
- name: Extract deploy metadata
run: |
echo "SHA=${{ github.sha }}" >> $GITHUB_ENV
echo "TAG=${{ github.ref_name }}" >> $GITHUB_ENV
echo "REPO=${{ github.repository }}" >> $GITHUB_ENV
- name: Update Salesforce Project
run: |
curl -X PATCH "$SALESFORCE_WEBHOOK_URL/services/data/v58.0/sobjects/Project__c/ExternalId__c/${{ secrets.PROJECT_KEY }}" \
-H "Authorization: Bearer ${{ secrets.SALESFORCE_ACCESS_TOKEN }}" \
-H "Content-Type: application/json" \
-d '{
"Last_Deploy_At__c": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'",
"Last_Deploy_SHA__c": "'$SHA'",
"Last_Deploy_Tag__c": "'$TAG'",
"Environment__c": "production",
"Release_Notes_URL__c": "https://github.com/'$REPO'/releases/tag/'$TAG'"
}'
- name: Complete Asana deploy task
run: |
TASK_GID=$(curl -s "https://app.asana.com/api/1.0/projects/${{ secrets.ASANA_PROJECT_GID }}/tasks?opt_fields=name,gid" \
-H "Authorization: Bearer ${{ secrets.ASANA_PAT }}" | \
jq -r '.data[] | select(.name | contains("Deploy to production")) | .gid')
curl -X PUT "https://app.asana.com/api/1.0/tasks/$TASK_GID" \
-H "Authorization: Bearer ${{ secrets.ASANA_PAT }}" \
-H "Content-Type: application/json" \
-d '{"data": {"completed": true}}'
# Post comment
curl -X POST "https://app.asana.com/api/1.0/tasks/$TASK_GID/stories" \
-H "Authorization: Bearer ${{ secrets.ASANA_PAT }}" \
-H "Content-Type: application/json" \
-d '{"data": {"text": "✅ Deployed '$TAG' from '$REPO'@'$SHA' to production"}}'
```
**Outputs:**
✅ Salesforce Project updated with latest deploy info
✅ Asana tasks auto-completed
✅ Asana comments added with deploy details
---
## Human Touch Points (Brenda View)
**What You See:**
1. **Day 0 Deal Closes:**
- You mark Opportunity as "Closed Won" in Salesforce
- Wait 5-10 minutes ⏱️
2. **Day 0 Automatic Magic:**
- Repos appear in GitHub (you get a Slack notification)
- Asana project appears with all tasks assigned
- No manual work needed
3. **Throughout Project:**
- Engineers merge code → tasks auto-update in Asana
- Deploy happens → Salesforce shows "Last Deploy: 2 hours ago"
- You just monitor Asana and communicate with client
**If Something Breaks:**
- Create an issue in `blackroad-sop` repo with label `automation-bug`
- Tag @ops or @devops team
- Include: Salesforce Project URL, expected vs. actual behavior
---
## Validation Checklist
After this workflow runs, verify:
- [ ] Project record exists in Salesforce with all fields populated
- [ ] 3 repos created in GitHub with correct naming
- [ ] CI/CD workflows present in each repo
- [ ] Branch protection enabled on `main`
- [ ] Asana project created with 8+ tasks
- [ ] Salesforce Project has Asana + GitHub URLs filled in
- [ ] Test deploy updates Salesforce + Asana correctly
---
## Troubleshooting
| Symptom | Likely Cause | Fix |
|---------|-------------|-----|
| No repos created after 10 min | GitHub API token expired | Refresh token in Salesforce Named Credential |
| Asana project missing | Webhook failed | Check Salesforce debug logs for HTTP callout errors |
| Deploy doesn't update Salesforce | Missing secret `PROJECT_KEY` | Add `PROJECT_KEY` secret to each repo |
| Tasks not auto-completing | Asana API token wrong | Verify `ASANA_PAT` secret in GitHub |
---
## Related Docs
- [Salesforce Flow Spec: Opp_Automation_OnStageChange](../salesforce/flows/opp-automation-onstagechange.md)
- [Salesforce Orchestration Spec](../salesforce/orchestrations/new-client-kickoff-orchestration.md)
- [GitHub Actions: CI Baseline](../templates/github-actions/ci.yml)
- [Integration: Salesforce → GitHub](../integrations/salesforce-to-github.md)
- [Integration: GitHub → Salesforce](../integrations/github-to-salesforce.md)
- [Playbook: Brenda's New Client Checklist](../playbooks/brenda-new-client-checklist.md)
---
## Metrics to Track
- **Time to First Commit:** From Closed Won to first commit in project repo (target: < 48 hours)
- **Automation Success Rate:** % of deals that auto-create repos + Asana (target: > 95%)
- **Manual Intervention Rate:** % of projects requiring manual fixes (target: < 10%)
- **Deploy Frequency:** Avg deploys per week per project (target: > 5)
---
**This is the backbone.** Every other automation workflow should follow this pattern:
1. Event-driven trigger
2. Cross-system orchestration
3. Feedback loops
4. Human-friendly visibility

View File

@@ -0,0 +1,556 @@
# 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.